From af034a9c5eec379c823a637b33d89f23a6a2716a Mon Sep 17 00:00:00 2001 From: Nate McMaster Date: Tue, 30 Oct 2018 14:18:11 -0700 Subject: [PATCH 0001/1101] Reorganize source code In preparation for merging many other projects into this repo, this establishes a new source code organization which groups projects together based on subject matter. \n\nCommit migrated from https://github.com/dotnet/extensions/commit/7ce647cfa3287e31497b72643eee28531eed1b7f --- src/ObjectPool/src/DefaultObjectPool.cs | 108 +++ .../src/DefaultObjectPoolProvider.cs | 17 + .../src/DefaultPooledObjectPolicy.cs | 20 + src/ObjectPool/src/IPooledObjectPolicy.cs | 12 + src/ObjectPool/src/LeakTrackingObjectPool.cs | 69 ++ .../src/LeakTrackingObjectPoolProvider.cs | 28 + .../Microsoft.Extensions.ObjectPool.csproj | 11 + src/ObjectPool/src/ObjectPool.cs | 12 + src/ObjectPool/src/ObjectPoolProvider.cs | 15 + .../src/ObjectPoolProviderExtensions.cs | 29 + src/ObjectPool/src/PooledObjectPolicy.cs | 12 + .../src/StringBuilderPooledObjectPolicy.cs | 31 + src/ObjectPool/src/baseline.netcore.json | 612 +++++++++++++ src/ObjectPool/test/DefaultObjectPoolTest.cs | 87 ++ ...crosoft.Extensions.ObjectPool.Tests.csproj | 11 + .../AspNetCoreBenchmarkAttribute.cs | 39 + .../BenchmarkRunner/DefaultCoreConfig.cs | 36 + .../DefaultCoreValidationConfig.cs | 25 + .../BenchmarkRunner/Directory.Build.props | 8 + .../ParameterizedJobConfigAttribute.cs | 15 + src/Shared/BenchmarkRunner/Program.cs | 88 ++ .../CertificateManager.cs | 720 +++++++++++++++ .../CertificatePurpose.cs | 12 + .../Directory.Build.props | 8 + .../EnsureCertificateResult.cs | 20 + .../ClosedGenericMatcher.cs | 106 +++ .../CommandLine/AnsiConsole.cs | 143 +++ .../CommandLine/CommandArgument.cs | 29 + .../CommandLine/CommandLineApplication.cs | 555 ++++++++++++ .../CommandLine/CommandOption.cs | 108 +++ .../CommandLine/CommandOptionType.cs | 13 + .../CommandLine/CommandParsingException.cs | 18 + ...Extensions.CommandLineUtils.Sources.shproj | 13 + .../Utilities/ArgumentEscaper.cs | 109 +++ .../CommandLineUtils/Utilities/DotNetMuxer.cs | 58 ++ .../CopyOnWriteDictionary.cs | 155 ++++ .../CopyOnWriteDictionaryHolder.cs | 166 ++++ .../HashCodeCombiner/HashCodeCombiner.cs | 84 ++ .../ObjectMethodExecutor/AwaitableInfo.cs | 127 +++ .../CoercedAwaitableInfo.cs | 55 ++ .../ObjectMethodExecutor.cs | 340 +++++++ .../ObjectMethodExecutorAwaitable.cs | 114 +++ .../ObjectMethodExecutorFSharpSupport.cs | 151 ++++ src/Shared/Process/ProcessHelper.cs | 113 +++ .../PropertyActivator/PropertyActivator.cs | 110 +++ src/Shared/PropertyHelper/PropertyHelper.cs | 526 +++++++++++ src/Shared/RazorViews/AttributeValue.cs | 38 + src/Shared/RazorViews/BaseView.cs | 279 ++++++ src/Shared/RazorViews/HelperResult.cs | 34 + src/Shared/SecurityHelper/SecurityHelper.cs | 40 + .../ExceptionDetails/ExceptionDetails.cs | 29 + .../ExceptionDetailsProvider.cs | 170 ++++ .../StackFrame/MethodDisplayInfo.cs | 49 ++ .../StackFrame/ParameterDisplayInfo.cs | 33 + .../StackFrame/PortablePdbReader.cs | 135 +++ .../StackTrace/StackFrame/StackFrameInfo.cs | 18 + .../StackFrame/StackFrameSourceCodeInfo.cs | 54 ++ .../StackTrace/StackFrame/StackTraceHelper.cs | 261 ++++++ src/Shared/TypeNameHelper/TypeNameHelper.cs | 160 ++++ src/Shared/ValueStopwatch/ValueStopwatch.cs | 39 + .../Properties/EncoderResources.cs | 38 + src/Shared/WebEncoders/WebEncoders.cs | 388 ++++++++ .../test/Shared.Tests/ArgumentEscaperTests.cs | 24 + .../Shared.Tests/CertificateManagerTests.cs | 304 +++++++ .../Shared.Tests/ClosedGenericMatcherTest.cs | 360 ++++++++ .../CommandLineApplicationTests.cs | 693 +++++++++++++++ .../CopyOnWriteDictionaryHolderTest.cs | 91 ++ .../Shared.Tests/CopyOnWriteDictionaryTest.cs | 109 +++ .../test/Shared.Tests/DotNetMuxerTests.cs | 24 + .../test/Shared.Tests/HashCodeCombinerTest.cs | 39 + .../Microsoft.AspNetCore.Shared.Tests.csproj | 32 + .../Shared.Tests/ObjectMethodExecutorTest.cs | 634 +++++++++++++ .../Shared.Tests/PropertyActivatorTest.cs | 187 ++++ .../test/Shared.Tests/PropertyHelperTest.cs | 831 ++++++++++++++++++ src/Shared/test/Shared.Tests/Readme.txt | 4 + .../test/Shared.Tests/SecurityHelperTests.cs | 93 ++ .../test/Shared.Tests/StackTraceHelperTest.cs | 345 ++++++++ .../test/Shared.Tests/TypeNameHelperTest.cs | 263 ++++++ .../test/Shared.Tests/ValueStopwatchTest.cs | 39 + .../test/Shared.Tests/WebEncodersTests.cs | 113 +++ .../testassets/ThrowingLibrary/Thrower.cs | 20 + .../ThrowingLibrary/ThrowingLibrary.csproj | 8 + 82 files changed, 11116 insertions(+) create mode 100644 src/ObjectPool/src/DefaultObjectPool.cs create mode 100644 src/ObjectPool/src/DefaultObjectPoolProvider.cs create mode 100644 src/ObjectPool/src/DefaultPooledObjectPolicy.cs create mode 100644 src/ObjectPool/src/IPooledObjectPolicy.cs create mode 100644 src/ObjectPool/src/LeakTrackingObjectPool.cs create mode 100644 src/ObjectPool/src/LeakTrackingObjectPoolProvider.cs create mode 100644 src/ObjectPool/src/Microsoft.Extensions.ObjectPool.csproj create mode 100644 src/ObjectPool/src/ObjectPool.cs create mode 100644 src/ObjectPool/src/ObjectPoolProvider.cs create mode 100644 src/ObjectPool/src/ObjectPoolProviderExtensions.cs create mode 100644 src/ObjectPool/src/PooledObjectPolicy.cs create mode 100644 src/ObjectPool/src/StringBuilderPooledObjectPolicy.cs create mode 100644 src/ObjectPool/src/baseline.netcore.json create mode 100644 src/ObjectPool/test/DefaultObjectPoolTest.cs create mode 100644 src/ObjectPool/test/Microsoft.Extensions.ObjectPool.Tests.csproj create mode 100644 src/Shared/BenchmarkRunner/AspNetCoreBenchmarkAttribute.cs create mode 100644 src/Shared/BenchmarkRunner/DefaultCoreConfig.cs create mode 100644 src/Shared/BenchmarkRunner/DefaultCoreValidationConfig.cs create mode 100644 src/Shared/BenchmarkRunner/Directory.Build.props create mode 100644 src/Shared/BenchmarkRunner/ParameterizedJobConfigAttribute.cs create mode 100644 src/Shared/BenchmarkRunner/Program.cs create mode 100644 src/Shared/CertificateGeneration/CertificateManager.cs create mode 100644 src/Shared/CertificateGeneration/CertificatePurpose.cs create mode 100644 src/Shared/CertificateGeneration/Directory.Build.props create mode 100644 src/Shared/CertificateGeneration/EnsureCertificateResult.cs create mode 100644 src/Shared/ClosedGenericMatcher/ClosedGenericMatcher.cs create mode 100644 src/Shared/CommandLineUtils/CommandLine/AnsiConsole.cs create mode 100644 src/Shared/CommandLineUtils/CommandLine/CommandArgument.cs create mode 100644 src/Shared/CommandLineUtils/CommandLine/CommandLineApplication.cs create mode 100644 src/Shared/CommandLineUtils/CommandLine/CommandOption.cs create mode 100644 src/Shared/CommandLineUtils/CommandLine/CommandOptionType.cs create mode 100644 src/Shared/CommandLineUtils/CommandLine/CommandParsingException.cs create mode 100644 src/Shared/CommandLineUtils/Microsoft.Extensions.CommandLineUtils.Sources.shproj create mode 100644 src/Shared/CommandLineUtils/Utilities/ArgumentEscaper.cs create mode 100644 src/Shared/CommandLineUtils/Utilities/DotNetMuxer.cs create mode 100644 src/Shared/CopyOnWriteDictionary/CopyOnWriteDictionary.cs create mode 100644 src/Shared/CopyOnWriteDictionary/CopyOnWriteDictionaryHolder.cs create mode 100644 src/Shared/HashCodeCombiner/HashCodeCombiner.cs create mode 100644 src/Shared/ObjectMethodExecutor/AwaitableInfo.cs create mode 100644 src/Shared/ObjectMethodExecutor/CoercedAwaitableInfo.cs create mode 100644 src/Shared/ObjectMethodExecutor/ObjectMethodExecutor.cs create mode 100644 src/Shared/ObjectMethodExecutor/ObjectMethodExecutorAwaitable.cs create mode 100644 src/Shared/ObjectMethodExecutor/ObjectMethodExecutorFSharpSupport.cs create mode 100644 src/Shared/Process/ProcessHelper.cs create mode 100644 src/Shared/PropertyActivator/PropertyActivator.cs create mode 100644 src/Shared/PropertyHelper/PropertyHelper.cs create mode 100644 src/Shared/RazorViews/AttributeValue.cs create mode 100644 src/Shared/RazorViews/BaseView.cs create mode 100644 src/Shared/RazorViews/HelperResult.cs create mode 100644 src/Shared/SecurityHelper/SecurityHelper.cs create mode 100644 src/Shared/StackTrace/ExceptionDetails/ExceptionDetails.cs create mode 100644 src/Shared/StackTrace/ExceptionDetails/ExceptionDetailsProvider.cs create mode 100644 src/Shared/StackTrace/StackFrame/MethodDisplayInfo.cs create mode 100644 src/Shared/StackTrace/StackFrame/ParameterDisplayInfo.cs create mode 100644 src/Shared/StackTrace/StackFrame/PortablePdbReader.cs create mode 100644 src/Shared/StackTrace/StackFrame/StackFrameInfo.cs create mode 100644 src/Shared/StackTrace/StackFrame/StackFrameSourceCodeInfo.cs create mode 100644 src/Shared/StackTrace/StackFrame/StackTraceHelper.cs create mode 100644 src/Shared/TypeNameHelper/TypeNameHelper.cs create mode 100644 src/Shared/ValueStopwatch/ValueStopwatch.cs create mode 100644 src/Shared/WebEncoders/Properties/EncoderResources.cs create mode 100644 src/Shared/WebEncoders/WebEncoders.cs create mode 100644 src/Shared/test/Shared.Tests/ArgumentEscaperTests.cs create mode 100644 src/Shared/test/Shared.Tests/CertificateManagerTests.cs create mode 100644 src/Shared/test/Shared.Tests/ClosedGenericMatcherTest.cs create mode 100644 src/Shared/test/Shared.Tests/CommandLineApplicationTests.cs create mode 100644 src/Shared/test/Shared.Tests/CopyOnWriteDictionaryHolderTest.cs create mode 100644 src/Shared/test/Shared.Tests/CopyOnWriteDictionaryTest.cs create mode 100644 src/Shared/test/Shared.Tests/DotNetMuxerTests.cs create mode 100644 src/Shared/test/Shared.Tests/HashCodeCombinerTest.cs create mode 100644 src/Shared/test/Shared.Tests/Microsoft.AspNetCore.Shared.Tests.csproj create mode 100644 src/Shared/test/Shared.Tests/ObjectMethodExecutorTest.cs create mode 100644 src/Shared/test/Shared.Tests/PropertyActivatorTest.cs create mode 100644 src/Shared/test/Shared.Tests/PropertyHelperTest.cs create mode 100644 src/Shared/test/Shared.Tests/Readme.txt create mode 100644 src/Shared/test/Shared.Tests/SecurityHelperTests.cs create mode 100644 src/Shared/test/Shared.Tests/StackTraceHelperTest.cs create mode 100644 src/Shared/test/Shared.Tests/TypeNameHelperTest.cs create mode 100644 src/Shared/test/Shared.Tests/ValueStopwatchTest.cs create mode 100644 src/Shared/test/Shared.Tests/WebEncodersTests.cs create mode 100644 src/Shared/test/testassets/ThrowingLibrary/Thrower.cs create mode 100644 src/Shared/test/testassets/ThrowingLibrary/ThrowingLibrary.csproj diff --git a/src/ObjectPool/src/DefaultObjectPool.cs b/src/ObjectPool/src/DefaultObjectPool.cs new file mode 100644 index 0000000000..87825967ac --- /dev/null +++ b/src/ObjectPool/src/DefaultObjectPool.cs @@ -0,0 +1,108 @@ +// 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.Diagnostics; +using System.Runtime.CompilerServices; +using System.Threading; + +namespace Microsoft.Extensions.ObjectPool +{ + public class DefaultObjectPool : ObjectPool where T : class + { + private readonly ObjectWrapper[] _items; + private readonly IPooledObjectPolicy _policy; + private readonly bool _isDefaultPolicy; + private T _firstItem; + + // This class was introduced in 2.1 to avoid the interface call where possible + private readonly PooledObjectPolicy _fastPolicy; + + public DefaultObjectPool(IPooledObjectPolicy policy) + : this(policy, Environment.ProcessorCount * 2) + { + } + + public DefaultObjectPool(IPooledObjectPolicy policy, int maximumRetained) + { + _policy = policy ?? throw new ArgumentNullException(nameof(policy)); + _fastPolicy = policy as PooledObjectPolicy; + _isDefaultPolicy = IsDefaultPolicy(); + + // -1 due to _firstItem + _items = new ObjectWrapper[maximumRetained - 1]; + + bool IsDefaultPolicy() + { + var type = policy.GetType(); + + return type.IsGenericType && type.GetGenericTypeDefinition() == typeof(DefaultPooledObjectPolicy<>); + } + } + + public override T Get() + { + T item = _firstItem; + + if (item == null || Interlocked.CompareExchange(ref _firstItem, null, item) != item) + { + item = GetViaScan(); + } + + return item; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private T GetViaScan() + { + ObjectWrapper[] items = _items; + + for (var i = 0; i < items.Length; i++) + { + T item = items[i]; + + if (item != null && Interlocked.CompareExchange(ref items[i].Element, null, item) == item) + { + return item; + } + } + + return Create(); + } + + // Non-inline to improve its code quality as uncommon path + [MethodImpl(MethodImplOptions.NoInlining)] + private T Create() => _fastPolicy?.Create() ?? _policy.Create(); + + public override void Return(T obj) + { + if (_isDefaultPolicy || (_fastPolicy?.Return(obj) ?? _policy.Return(obj))) + { + if (_firstItem != null || Interlocked.CompareExchange(ref _firstItem, obj, null) != null) + { + ReturnViaScan(obj); + } + } + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private void ReturnViaScan(T obj) + { + ObjectWrapper[] items = _items; + + for (var i = 0; i < items.Length && Interlocked.CompareExchange(ref items[i].Element, obj, null) != null; ++i) + { + } + } + + [DebuggerDisplay("{Element}")] + private struct ObjectWrapper + { + public T Element; + + public ObjectWrapper(T item) => Element = item; + + public static implicit operator T(ObjectWrapper wrapper) => wrapper.Element; + } + } +} diff --git a/src/ObjectPool/src/DefaultObjectPoolProvider.cs b/src/ObjectPool/src/DefaultObjectPoolProvider.cs new file mode 100644 index 0000000000..fb3c4bfa7e --- /dev/null +++ b/src/ObjectPool/src/DefaultObjectPoolProvider.cs @@ -0,0 +1,17 @@ +// 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; + +namespace Microsoft.Extensions.ObjectPool +{ + public class DefaultObjectPoolProvider : ObjectPoolProvider + { + public int MaximumRetained { get; set; } = Environment.ProcessorCount * 2; + + public override ObjectPool Create(IPooledObjectPolicy policy) + { + return new DefaultObjectPool(policy, MaximumRetained); + } + } +} diff --git a/src/ObjectPool/src/DefaultPooledObjectPolicy.cs b/src/ObjectPool/src/DefaultPooledObjectPolicy.cs new file mode 100644 index 0000000000..a7c386ae2a --- /dev/null +++ b/src/ObjectPool/src/DefaultPooledObjectPolicy.cs @@ -0,0 +1,20 @@ +// 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. + +namespace Microsoft.Extensions.ObjectPool +{ + public class DefaultPooledObjectPolicy : PooledObjectPolicy where T : class, new() + { + public override T Create() + { + return new T(); + } + + // DefaultObjectPool doesn't call 'Return' for the default policy. + // So take care adding any logic to this method, as it might require changes elsewhere. + public override bool Return(T obj) + { + return true; + } + } +} diff --git a/src/ObjectPool/src/IPooledObjectPolicy.cs b/src/ObjectPool/src/IPooledObjectPolicy.cs new file mode 100644 index 0000000000..54611bad30 --- /dev/null +++ b/src/ObjectPool/src/IPooledObjectPolicy.cs @@ -0,0 +1,12 @@ +// 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. + +namespace Microsoft.Extensions.ObjectPool +{ + public interface IPooledObjectPolicy + { + T Create(); + + bool Return(T obj); + } +} diff --git a/src/ObjectPool/src/LeakTrackingObjectPool.cs b/src/ObjectPool/src/LeakTrackingObjectPool.cs new file mode 100644 index 0000000000..243d44d2da --- /dev/null +++ b/src/ObjectPool/src/LeakTrackingObjectPool.cs @@ -0,0 +1,69 @@ +// 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.Diagnostics; +using System.Runtime.CompilerServices; + +namespace Microsoft.Extensions.ObjectPool +{ + public class LeakTrackingObjectPool : ObjectPool where T : class + { + private readonly ConditionalWeakTable _trackers = new ConditionalWeakTable(); + private readonly ObjectPool _inner; + + public LeakTrackingObjectPool(ObjectPool inner) + { + if (inner == null) + { + throw new ArgumentNullException(nameof(inner)); + } + + _inner = inner; + } + + public override T Get() + { + var value = _inner.Get(); + _trackers.Add(value, new Tracker()); + return value; + } + + public override void Return(T obj) + { + Tracker tracker; + if (_trackers.TryGetValue(obj, out tracker)) + { + _trackers.Remove(obj); + tracker.Dispose(); + } + + _inner.Return(obj); + } + + private class Tracker : IDisposable + { + private readonly string _stack; + private bool _disposed; + + public Tracker() + { + _stack = Environment.StackTrace; + } + + public void Dispose() + { + _disposed = true; + GC.SuppressFinalize(this); + } + + ~Tracker() + { + if (!_disposed && !Environment.HasShutdownStarted) + { + Debug.Fail($"{typeof(T).Name} was leaked. Created at: {Environment.NewLine}{_stack}"); + } + } + } + } +} diff --git a/src/ObjectPool/src/LeakTrackingObjectPoolProvider.cs b/src/ObjectPool/src/LeakTrackingObjectPoolProvider.cs new file mode 100644 index 0000000000..134eaf161c --- /dev/null +++ b/src/ObjectPool/src/LeakTrackingObjectPoolProvider.cs @@ -0,0 +1,28 @@ +// 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; + +namespace Microsoft.Extensions.ObjectPool +{ + public class LeakTrackingObjectPoolProvider : ObjectPoolProvider + { + private readonly ObjectPoolProvider _inner; + + public LeakTrackingObjectPoolProvider(ObjectPoolProvider inner) + { + if (inner == null) + { + throw new ArgumentNullException(nameof(inner)); + } + + _inner = inner; + } + + public override ObjectPool Create(IPooledObjectPolicy policy) + { + var inner = _inner.Create(policy); + return new LeakTrackingObjectPool(inner); + } + } +} diff --git a/src/ObjectPool/src/Microsoft.Extensions.ObjectPool.csproj b/src/ObjectPool/src/Microsoft.Extensions.ObjectPool.csproj new file mode 100644 index 0000000000..cb42c5615a --- /dev/null +++ b/src/ObjectPool/src/Microsoft.Extensions.ObjectPool.csproj @@ -0,0 +1,11 @@ + + + + A simple object pool implementation. + netstandard2.0 + $(NoWarn);CS1591 + true + pooling + + + diff --git a/src/ObjectPool/src/ObjectPool.cs b/src/ObjectPool/src/ObjectPool.cs new file mode 100644 index 0000000000..8cf52c9195 --- /dev/null +++ b/src/ObjectPool/src/ObjectPool.cs @@ -0,0 +1,12 @@ +// 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. + +namespace Microsoft.Extensions.ObjectPool +{ + public abstract class ObjectPool where T : class + { + public abstract T Get(); + + public abstract void Return(T obj); + } +} diff --git a/src/ObjectPool/src/ObjectPoolProvider.cs b/src/ObjectPool/src/ObjectPoolProvider.cs new file mode 100644 index 0000000000..909795dd35 --- /dev/null +++ b/src/ObjectPool/src/ObjectPoolProvider.cs @@ -0,0 +1,15 @@ +// 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. + +namespace Microsoft.Extensions.ObjectPool +{ + public abstract class ObjectPoolProvider + { + public ObjectPool Create() where T : class, new() + { + return Create(new DefaultPooledObjectPolicy()); + } + + public abstract ObjectPool Create(IPooledObjectPolicy policy) where T : class; + } +} diff --git a/src/ObjectPool/src/ObjectPoolProviderExtensions.cs b/src/ObjectPool/src/ObjectPoolProviderExtensions.cs new file mode 100644 index 0000000000..b9e9359889 --- /dev/null +++ b/src/ObjectPool/src/ObjectPoolProviderExtensions.cs @@ -0,0 +1,29 @@ +// 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.Text; + +namespace Microsoft.Extensions.ObjectPool +{ + public static class ObjectPoolProviderExtensions + { + public static ObjectPool CreateStringBuilderPool(this ObjectPoolProvider provider) + { + return provider.Create(new StringBuilderPooledObjectPolicy()); + } + + public static ObjectPool CreateStringBuilderPool( + this ObjectPoolProvider provider, + int initialCapacity, + int maximumRetainedCapacity) + { + var policy = new StringBuilderPooledObjectPolicy() + { + InitialCapacity = initialCapacity, + MaximumRetainedCapacity = maximumRetainedCapacity, + }; + + return provider.Create(policy); + } + } +} diff --git a/src/ObjectPool/src/PooledObjectPolicy.cs b/src/ObjectPool/src/PooledObjectPolicy.cs new file mode 100644 index 0000000000..855b764967 --- /dev/null +++ b/src/ObjectPool/src/PooledObjectPolicy.cs @@ -0,0 +1,12 @@ +// 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. + +namespace Microsoft.Extensions.ObjectPool +{ + public abstract class PooledObjectPolicy : IPooledObjectPolicy + { + public abstract T Create(); + + public abstract bool Return(T obj); + } +} diff --git a/src/ObjectPool/src/StringBuilderPooledObjectPolicy.cs b/src/ObjectPool/src/StringBuilderPooledObjectPolicy.cs new file mode 100644 index 0000000000..94f318729a --- /dev/null +++ b/src/ObjectPool/src/StringBuilderPooledObjectPolicy.cs @@ -0,0 +1,31 @@ +// 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.Text; + +namespace Microsoft.Extensions.ObjectPool +{ + public class StringBuilderPooledObjectPolicy : PooledObjectPolicy + { + public int InitialCapacity { get; set; } = 100; + + public int MaximumRetainedCapacity { get; set; } = 4 * 1024; + + public override StringBuilder Create() + { + return new StringBuilder(InitialCapacity); + } + + public override bool Return(StringBuilder obj) + { + if (obj.Capacity > MaximumRetainedCapacity) + { + // Too big. Discard this one. + return false; + } + + obj.Clear(); + return true; + } + } +} diff --git a/src/ObjectPool/src/baseline.netcore.json b/src/ObjectPool/src/baseline.netcore.json new file mode 100644 index 0000000000..253c1f6b66 --- /dev/null +++ b/src/ObjectPool/src/baseline.netcore.json @@ -0,0 +1,612 @@ +{ + "AssemblyIdentity": "Microsoft.Extensions.ObjectPool, Version=2.1.1.0, Culture=neutral, PublicKeyToken=adb9793829ddae60", + "Types": [ + { + "Name": "Microsoft.Extensions.ObjectPool.DefaultObjectPool", + "Visibility": "Public", + "Kind": "Class", + "BaseType": "Microsoft.Extensions.ObjectPool.ObjectPool", + "ImplementedInterfaces": [], + "Members": [ + { + "Kind": "Method", + "Name": "Get", + "Parameters": [], + "ReturnType": "T0", + "Virtual": true, + "Override": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "Return", + "Parameters": [ + { + "Name": "obj", + "Type": "T0" + } + ], + "ReturnType": "System.Void", + "Virtual": true, + "Override": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Constructor", + "Name": ".ctor", + "Parameters": [ + { + "Name": "policy", + "Type": "Microsoft.Extensions.ObjectPool.IPooledObjectPolicy" + } + ], + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Constructor", + "Name": ".ctor", + "Parameters": [ + { + "Name": "policy", + "Type": "Microsoft.Extensions.ObjectPool.IPooledObjectPolicy" + }, + { + "Name": "maximumRetained", + "Type": "System.Int32" + } + ], + "Visibility": "Public", + "GenericParameter": [] + } + ], + "GenericParameters": [ + { + "ParameterName": "T", + "ParameterPosition": 0, + "Class": true, + "BaseTypeOrInterfaces": [] + } + ] + }, + { + "Name": "Microsoft.Extensions.ObjectPool.DefaultObjectPoolProvider", + "Visibility": "Public", + "Kind": "Class", + "BaseType": "Microsoft.Extensions.ObjectPool.ObjectPoolProvider", + "ImplementedInterfaces": [], + "Members": [ + { + "Kind": "Method", + "Name": "get_MaximumRetained", + "Parameters": [], + "ReturnType": "System.Int32", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "set_MaximumRetained", + "Parameters": [ + { + "Name": "value", + "Type": "System.Int32" + } + ], + "ReturnType": "System.Void", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "Create", + "Parameters": [ + { + "Name": "policy", + "Type": "Microsoft.Extensions.ObjectPool.IPooledObjectPolicy" + } + ], + "ReturnType": "Microsoft.Extensions.ObjectPool.ObjectPool", + "Virtual": true, + "Override": true, + "Visibility": "Public", + "GenericParameter": [ + { + "ParameterName": "T", + "ParameterPosition": 0, + "Class": true, + "BaseTypeOrInterfaces": [] + } + ] + }, + { + "Kind": "Constructor", + "Name": ".ctor", + "Parameters": [], + "Visibility": "Public", + "GenericParameter": [] + } + ], + "GenericParameters": [] + }, + { + "Name": "Microsoft.Extensions.ObjectPool.DefaultPooledObjectPolicy", + "Visibility": "Public", + "Kind": "Class", + "BaseType": "Microsoft.Extensions.ObjectPool.PooledObjectPolicy", + "ImplementedInterfaces": [], + "Members": [ + { + "Kind": "Method", + "Name": "Create", + "Parameters": [], + "ReturnType": "T0", + "Virtual": true, + "Override": true, + "ImplementedInterface": "Microsoft.Extensions.ObjectPool.IPooledObjectPolicy", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "Return", + "Parameters": [ + { + "Name": "obj", + "Type": "T0" + } + ], + "ReturnType": "System.Boolean", + "Virtual": true, + "Override": true, + "ImplementedInterface": "Microsoft.Extensions.ObjectPool.IPooledObjectPolicy", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Constructor", + "Name": ".ctor", + "Parameters": [], + "Visibility": "Public", + "GenericParameter": [] + } + ], + "GenericParameters": [ + { + "ParameterName": "T", + "ParameterPosition": 0, + "New": true, + "Class": true, + "BaseTypeOrInterfaces": [] + } + ] + }, + { + "Name": "Microsoft.Extensions.ObjectPool.IPooledObjectPolicy", + "Visibility": "Public", + "Kind": "Interface", + "Abstract": true, + "ImplementedInterfaces": [], + "Members": [ + { + "Kind": "Method", + "Name": "Create", + "Parameters": [], + "ReturnType": "T0", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "Return", + "Parameters": [ + { + "Name": "obj", + "Type": "T0" + } + ], + "ReturnType": "System.Boolean", + "GenericParameter": [] + } + ], + "GenericParameters": [ + { + "ParameterName": "T", + "ParameterPosition": 0, + "BaseTypeOrInterfaces": [] + } + ] + }, + { + "Name": "Microsoft.Extensions.ObjectPool.LeakTrackingObjectPool", + "Visibility": "Public", + "Kind": "Class", + "BaseType": "Microsoft.Extensions.ObjectPool.ObjectPool", + "ImplementedInterfaces": [], + "Members": [ + { + "Kind": "Method", + "Name": "Get", + "Parameters": [], + "ReturnType": "T0", + "Virtual": true, + "Override": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "Return", + "Parameters": [ + { + "Name": "obj", + "Type": "T0" + } + ], + "ReturnType": "System.Void", + "Virtual": true, + "Override": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Constructor", + "Name": ".ctor", + "Parameters": [ + { + "Name": "inner", + "Type": "Microsoft.Extensions.ObjectPool.ObjectPool" + } + ], + "Visibility": "Public", + "GenericParameter": [] + } + ], + "GenericParameters": [ + { + "ParameterName": "T", + "ParameterPosition": 0, + "Class": true, + "BaseTypeOrInterfaces": [] + } + ] + }, + { + "Name": "Microsoft.Extensions.ObjectPool.LeakTrackingObjectPoolProvider", + "Visibility": "Public", + "Kind": "Class", + "BaseType": "Microsoft.Extensions.ObjectPool.ObjectPoolProvider", + "ImplementedInterfaces": [], + "Members": [ + { + "Kind": "Method", + "Name": "Create", + "Parameters": [ + { + "Name": "policy", + "Type": "Microsoft.Extensions.ObjectPool.IPooledObjectPolicy" + } + ], + "ReturnType": "Microsoft.Extensions.ObjectPool.ObjectPool", + "Virtual": true, + "Override": true, + "Visibility": "Public", + "GenericParameter": [ + { + "ParameterName": "T", + "ParameterPosition": 0, + "Class": true, + "BaseTypeOrInterfaces": [] + } + ] + }, + { + "Kind": "Constructor", + "Name": ".ctor", + "Parameters": [ + { + "Name": "inner", + "Type": "Microsoft.Extensions.ObjectPool.ObjectPoolProvider" + } + ], + "Visibility": "Public", + "GenericParameter": [] + } + ], + "GenericParameters": [] + }, + { + "Name": "Microsoft.Extensions.ObjectPool.ObjectPool", + "Visibility": "Public", + "Kind": "Class", + "Abstract": true, + "ImplementedInterfaces": [], + "Members": [ + { + "Kind": "Method", + "Name": "Get", + "Parameters": [], + "ReturnType": "T0", + "Virtual": true, + "Abstract": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "Return", + "Parameters": [ + { + "Name": "obj", + "Type": "T0" + } + ], + "ReturnType": "System.Void", + "Virtual": true, + "Abstract": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Constructor", + "Name": ".ctor", + "Parameters": [], + "Visibility": "Protected", + "GenericParameter": [] + } + ], + "GenericParameters": [ + { + "ParameterName": "T", + "ParameterPosition": 0, + "Class": true, + "BaseTypeOrInterfaces": [] + } + ] + }, + { + "Name": "Microsoft.Extensions.ObjectPool.ObjectPoolProvider", + "Visibility": "Public", + "Kind": "Class", + "Abstract": true, + "ImplementedInterfaces": [], + "Members": [ + { + "Kind": "Method", + "Name": "Create", + "Parameters": [ + { + "Name": "policy", + "Type": "Microsoft.Extensions.ObjectPool.IPooledObjectPolicy" + } + ], + "ReturnType": "Microsoft.Extensions.ObjectPool.ObjectPool", + "Virtual": true, + "Abstract": true, + "Visibility": "Public", + "GenericParameter": [ + { + "ParameterName": "T", + "ParameterPosition": 0, + "Class": true, + "BaseTypeOrInterfaces": [] + } + ] + }, + { + "Kind": "Method", + "Name": "Create", + "Parameters": [], + "ReturnType": "Microsoft.Extensions.ObjectPool.ObjectPool", + "Visibility": "Public", + "GenericParameter": [ + { + "ParameterName": "T", + "ParameterPosition": 0, + "New": true, + "Class": true, + "BaseTypeOrInterfaces": [] + } + ] + }, + { + "Kind": "Constructor", + "Name": ".ctor", + "Parameters": [], + "Visibility": "Protected", + "GenericParameter": [] + } + ], + "GenericParameters": [] + }, + { + "Name": "Microsoft.Extensions.ObjectPool.ObjectPoolProviderExtensions", + "Visibility": "Public", + "Kind": "Class", + "Abstract": true, + "Static": true, + "Sealed": true, + "ImplementedInterfaces": [], + "Members": [ + { + "Kind": "Method", + "Name": "CreateStringBuilderPool", + "Parameters": [ + { + "Name": "provider", + "Type": "Microsoft.Extensions.ObjectPool.ObjectPoolProvider" + } + ], + "ReturnType": "Microsoft.Extensions.ObjectPool.ObjectPool", + "Static": true, + "Extension": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "CreateStringBuilderPool", + "Parameters": [ + { + "Name": "provider", + "Type": "Microsoft.Extensions.ObjectPool.ObjectPoolProvider" + }, + { + "Name": "initialCapacity", + "Type": "System.Int32" + }, + { + "Name": "maximumRetainedCapacity", + "Type": "System.Int32" + } + ], + "ReturnType": "Microsoft.Extensions.ObjectPool.ObjectPool", + "Static": true, + "Extension": true, + "Visibility": "Public", + "GenericParameter": [] + } + ], + "GenericParameters": [] + }, + { + "Name": "Microsoft.Extensions.ObjectPool.PooledObjectPolicy", + "Visibility": "Public", + "Kind": "Class", + "Abstract": true, + "ImplementedInterfaces": [ + "Microsoft.Extensions.ObjectPool.IPooledObjectPolicy" + ], + "Members": [ + { + "Kind": "Method", + "Name": "Create", + "Parameters": [], + "ReturnType": "T0", + "Virtual": true, + "Abstract": true, + "ImplementedInterface": "Microsoft.Extensions.ObjectPool.IPooledObjectPolicy", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "Return", + "Parameters": [ + { + "Name": "obj", + "Type": "T0" + } + ], + "ReturnType": "System.Boolean", + "Virtual": true, + "Abstract": true, + "ImplementedInterface": "Microsoft.Extensions.ObjectPool.IPooledObjectPolicy", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Constructor", + "Name": ".ctor", + "Parameters": [], + "Visibility": "Protected", + "GenericParameter": [] + } + ], + "GenericParameters": [ + { + "ParameterName": "T", + "ParameterPosition": 0, + "BaseTypeOrInterfaces": [] + } + ] + }, + { + "Name": "Microsoft.Extensions.ObjectPool.StringBuilderPooledObjectPolicy", + "Visibility": "Public", + "Kind": "Class", + "BaseType": "Microsoft.Extensions.ObjectPool.PooledObjectPolicy", + "ImplementedInterfaces": [], + "Members": [ + { + "Kind": "Method", + "Name": "Create", + "Parameters": [], + "ReturnType": "System.Text.StringBuilder", + "Virtual": true, + "Override": true, + "ImplementedInterface": "Microsoft.Extensions.ObjectPool.IPooledObjectPolicy", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "Return", + "Parameters": [ + { + "Name": "obj", + "Type": "System.Text.StringBuilder" + } + ], + "ReturnType": "System.Boolean", + "Virtual": true, + "Override": true, + "ImplementedInterface": "Microsoft.Extensions.ObjectPool.IPooledObjectPolicy", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "get_InitialCapacity", + "Parameters": [], + "ReturnType": "System.Int32", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "set_InitialCapacity", + "Parameters": [ + { + "Name": "value", + "Type": "System.Int32" + } + ], + "ReturnType": "System.Void", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "get_MaximumRetainedCapacity", + "Parameters": [], + "ReturnType": "System.Int32", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "set_MaximumRetainedCapacity", + "Parameters": [ + { + "Name": "value", + "Type": "System.Int32" + } + ], + "ReturnType": "System.Void", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Constructor", + "Name": ".ctor", + "Parameters": [], + "Visibility": "Public", + "GenericParameter": [] + } + ], + "GenericParameters": [] + } + ] +} \ No newline at end of file diff --git a/src/ObjectPool/test/DefaultObjectPoolTest.cs b/src/ObjectPool/test/DefaultObjectPoolTest.cs new file mode 100644 index 0000000000..ca14a3f53b --- /dev/null +++ b/src/ObjectPool/test/DefaultObjectPoolTest.cs @@ -0,0 +1,87 @@ +// 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.Collections.Generic; +using System.Threading.Tasks; +using Xunit; + +namespace Microsoft.Extensions.ObjectPool.Test +{ + public class DefaultObjectPoolTest + { + [Fact] + public void DefaultObjectPoolWithDefaultPolicy_GetAnd_ReturnObject_SameInstance() + { + // Arrange + var pool = new DefaultObjectPool(new DefaultPooledObjectPolicy()); + + var obj1 = pool.Get(); + pool.Return(obj1); + + // Act + var obj2 = pool.Get(); + + // Assert + Assert.Same(obj1, obj2); + } + + [Fact] + public void DefaultObjectPool_GetAndReturnObject_SameInstance() + { + // Arrange + var pool = new DefaultObjectPool>(new ListPolicy()); + + var list1 = pool.Get(); + pool.Return(list1); + + // Act + var list2 = pool.Get(); + + // Assert + Assert.Same(list1, list2); + } + + [Fact] + public void DefaultObjectPool_CreatedByPolicy() + { + // Arrange + var pool = new DefaultObjectPool>(new ListPolicy()); + + // Act + var list = pool.Get(); + + // Assert + Assert.Equal(17, list.Capacity); + } + + [Fact] + public void DefaultObjectPool_Return_RejectedByPolicy() + { + // Arrange + var pool = new DefaultObjectPool>(new ListPolicy()); + var list1 = pool.Get(); + list1.Capacity = 20; + + // Act + pool.Return(list1); + var list2 = pool.Get(); + + // Assert + Assert.NotSame(list1, list2); + } + + private class ListPolicy : IPooledObjectPolicy> + { + public List Create() + { + return new List(17); + } + + public bool Return(List obj) + { + return obj.Capacity == 17; + } + } + } +} diff --git a/src/ObjectPool/test/Microsoft.Extensions.ObjectPool.Tests.csproj b/src/ObjectPool/test/Microsoft.Extensions.ObjectPool.Tests.csproj new file mode 100644 index 0000000000..b922cc227e --- /dev/null +++ b/src/ObjectPool/test/Microsoft.Extensions.ObjectPool.Tests.csproj @@ -0,0 +1,11 @@ + + + + $(StandardTestTfms) + + + + + + + diff --git a/src/Shared/BenchmarkRunner/AspNetCoreBenchmarkAttribute.cs b/src/Shared/BenchmarkRunner/AspNetCoreBenchmarkAttribute.cs new file mode 100644 index 0000000000..a4044d1b5e --- /dev/null +++ b/src/Shared/BenchmarkRunner/AspNetCoreBenchmarkAttribute.cs @@ -0,0 +1,39 @@ +// 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.Linq; +using System.Reflection; +using BenchmarkDotNet.Attributes; +using BenchmarkDotNet.Running; +using BenchmarkDotNet.Configs; +using BenchmarkDotNet.Jobs; +using BenchmarkDotNet.Toolchains.InProcess; + +namespace BenchmarkDotNet.Attributes +{ + [AttributeUsage(AttributeTargets.Class | AttributeTargets.Assembly)] + internal class AspNetCoreBenchmarkAttribute : Attribute, IConfigSource + { + public static bool UseValidationConfig { get; set; } + + public Type ConfigType { get; } + public Type ValidationConfigType { get; } + + public AspNetCoreBenchmarkAttribute() : this(typeof(DefaultCoreConfig)) + { + } + + public AspNetCoreBenchmarkAttribute(Type configType) : this(configType, typeof(DefaultCoreValidationConfig)) + { + } + + public AspNetCoreBenchmarkAttribute(Type configType, Type validationConfigType) + { + ConfigType = configType; + ValidationConfigType = validationConfigType; + } + + public IConfig Config => (IConfig) Activator.CreateInstance(UseValidationConfig ? ValidationConfigType : ConfigType, Array.Empty()); + } +} diff --git a/src/Shared/BenchmarkRunner/DefaultCoreConfig.cs b/src/Shared/BenchmarkRunner/DefaultCoreConfig.cs new file mode 100644 index 0000000000..a8d9d60536 --- /dev/null +++ b/src/Shared/BenchmarkRunner/DefaultCoreConfig.cs @@ -0,0 +1,36 @@ +// 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 BenchmarkDotNet.Columns; +using BenchmarkDotNet.Configs; +using BenchmarkDotNet.Diagnosers; +using BenchmarkDotNet.Engines; +using BenchmarkDotNet.Exporters; +using BenchmarkDotNet.Jobs; +using BenchmarkDotNet.Loggers; +using BenchmarkDotNet.Toolchains.CsProj; +using BenchmarkDotNet.Toolchains.DotNetCli; +using BenchmarkDotNet.Validators; + +namespace BenchmarkDotNet.Attributes +{ + internal class DefaultCoreConfig : ManualConfig + { + public DefaultCoreConfig() + { + Add(ConsoleLogger.Default); + Add(MarkdownExporter.GitHub); + + Add(MemoryDiagnoser.Default); + Add(StatisticColumn.OperationsPerSecond); + Add(DefaultColumnProviders.Instance); + + Add(JitOptimizationsValidator.FailOnError); + + Add(Job.Core + .With(CsProjCoreToolchain.From(NetCoreAppSettings.NetCoreApp21)) + .With(new GcMode { Server = true }) + .With(RunStrategy.Throughput)); + } + } +} diff --git a/src/Shared/BenchmarkRunner/DefaultCoreValidationConfig.cs b/src/Shared/BenchmarkRunner/DefaultCoreValidationConfig.cs new file mode 100644 index 0000000000..95fc725564 --- /dev/null +++ b/src/Shared/BenchmarkRunner/DefaultCoreValidationConfig.cs @@ -0,0 +1,25 @@ +// 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.Linq; +using System.Reflection; +using BenchmarkDotNet.Attributes; +using BenchmarkDotNet.Running; +using BenchmarkDotNet.Configs; +using BenchmarkDotNet.Jobs; +using BenchmarkDotNet.Loggers; +using BenchmarkDotNet.Toolchains.InProcess; + +namespace BenchmarkDotNet.Attributes +{ + internal class DefaultCoreValidationConfig : ManualConfig + { + public DefaultCoreValidationConfig() + { + Add(ConsoleLogger.Default); + + Add(Job.Dry.With(InProcessToolchain.Instance)); + } + } +} diff --git a/src/Shared/BenchmarkRunner/Directory.Build.props b/src/Shared/BenchmarkRunner/Directory.Build.props new file mode 100644 index 0000000000..34cf593291 --- /dev/null +++ b/src/Shared/BenchmarkRunner/Directory.Build.props @@ -0,0 +1,8 @@ + + + + + + Microsoft.Extensions.$(ProjectDirName).Sources + + diff --git a/src/Shared/BenchmarkRunner/ParameterizedJobConfigAttribute.cs b/src/Shared/BenchmarkRunner/ParameterizedJobConfigAttribute.cs new file mode 100644 index 0000000000..9e0f947dc7 --- /dev/null +++ b/src/Shared/BenchmarkRunner/ParameterizedJobConfigAttribute.cs @@ -0,0 +1,15 @@ +// 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; + +namespace BenchmarkDotNet.Attributes +{ + [AttributeUsage(AttributeTargets.Class | AttributeTargets.Assembly)] + internal class ParameterizedJobConfigAttribute: AspNetCoreBenchmarkAttribute + { + public ParameterizedJobConfigAttribute(Type configType) : base(configType) + { + } + } +} diff --git a/src/Shared/BenchmarkRunner/Program.cs b/src/Shared/BenchmarkRunner/Program.cs new file mode 100644 index 0000000000..3297d5dae9 --- /dev/null +++ b/src/Shared/BenchmarkRunner/Program.cs @@ -0,0 +1,88 @@ +// 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.IO; +using System.Linq; +using System.Reflection; +using System.Text; +using BenchmarkDotNet.Attributes; +using BenchmarkDotNet.Running; +using BenchmarkDotNet.Configs; +using BenchmarkDotNet.Jobs; +using BenchmarkDotNet.Toolchains.InProcess; + +namespace Microsoft.AspNetCore.BenchmarkDotNet.Runner +{ + partial class Program + { + private static TextWriter _standardOutput; + private static StringBuilder _standardOutputText; + + static partial void BeforeMain(string[] args); + + private static int Main(string[] args) + { + BeforeMain(args); + + CheckValidate(ref args); + var summaries = BenchmarkSwitcher.FromAssembly(typeof(Program).GetTypeInfo().Assembly) + .Run(args, ManualConfig.CreateEmpty()); + + foreach (var summary in summaries) + { + if (summary.HasCriticalValidationErrors) + { + return Fail(summary, nameof(summary.HasCriticalValidationErrors)); + } + + foreach (var report in summary.Reports) + { + if (!report.BuildResult.IsGenerateSuccess) + { + return Fail(report, nameof(report.BuildResult.IsGenerateSuccess)); + } + + if (!report.BuildResult.IsBuildSuccess) + { + return Fail(report, nameof(report.BuildResult.IsBuildSuccess)); + } + + if (!report.AllMeasurements.Any()) + { + return Fail(report, nameof(report.AllMeasurements)); + } + } + } + + return 0; + } + + private static int Fail(object o, string message) + { + _standardOutput?.WriteLine(_standardOutputText.ToString()); + + Console.Error.WriteLine("'{0}' failed, reason: '{1}'", o, message); + return 1; + } + + private static void CheckValidate(ref string[] args) + { + var argsList = args.ToList(); + if (argsList.Remove("--validate") || argsList.Remove("--validate-fast")) + { + SuppressConsole(); + AspNetCoreBenchmarkAttribute.UseValidationConfig = true; + } + + args = argsList.ToArray(); + } + + private static void SuppressConsole() + { + _standardOutput = Console.Out; + _standardOutputText = new StringBuilder(); + Console.SetOut(new StringWriter(_standardOutputText)); + } + } +} diff --git a/src/Shared/CertificateGeneration/CertificateManager.cs b/src/Shared/CertificateGeneration/CertificateManager.cs new file mode 100644 index 0000000000..4e2a0a9964 --- /dev/null +++ b/src/Shared/CertificateGeneration/CertificateManager.cs @@ -0,0 +1,720 @@ +// 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.Collections.Generic; +using System.Diagnostics; +using System.IO; +using System.Linq; +using System.Runtime.InteropServices; +using System.Security.Cryptography; +using System.Security.Cryptography.X509Certificates; +using System.Text; +using System.Text.RegularExpressions; + +namespace Microsoft.AspNetCore.Certificates.Generation +{ + internal class CertificateManager + { + public const string AspNetHttpsOid = "1.3.6.1.4.1.311.84.1.1"; + public const string AspNetHttpsOidFriendlyName = "ASP.NET Core HTTPS development certificate"; + + public const string AspNetIdentityOid = "1.3.6.1.4.1.311.84.1.2"; + public const string AspNetIdentityOidFriendlyName = "ASP.NET Core Identity Json Web Token signing development certificate"; + + private const string ServerAuthenticationEnhancedKeyUsageOid = "1.3.6.1.5.5.7.3.1"; + private const string ServerAuthenticationEnhancedKeyUsageOidFriendlyName = "Server Authentication"; + + private const string LocalhostHttpsDnsName = "localhost"; + private const string LocalhostHttpsDistinguishedName = "CN=" + LocalhostHttpsDnsName; + + private const string IdentityDistinguishedName = "CN=Microsoft.AspNetCore.Identity.Signing"; + + public const int RSAMinimumKeySizeInBits = 2048; + + private static readonly TimeSpan MaxRegexTimeout = TimeSpan.FromMinutes(1); + private const string CertificateSubjectRegex = "CN=(.*[^,]+).*"; + private const string MacOSSystemKeyChain = "/Library/Keychains/System.keychain"; + private static readonly string MacOSUserKeyChain = Environment.GetEnvironmentVariable("HOME") + "/Library/Keychains/login.keychain-db"; + private const string MacOSFindCertificateCommandLine = "security"; +#if NETCOREAPP2_0 || NETCOREAPP2_1 + private static readonly string MacOSFindCertificateCommandLineArgumentsFormat = "find-certificate -c {0} -a -Z -p " + MacOSSystemKeyChain; +#endif + private const string MacOSFindCertificateOutputRegex = "SHA-1 hash: ([0-9A-Z]+)"; + private const string MacOSRemoveCertificateTrustCommandLine = "sudo"; + private const string MacOSRemoveCertificateTrustCommandLineArgumentsFormat = "security remove-trusted-cert -d {0}"; + private const string MacOSDeleteCertificateCommandLine = "sudo"; + private const string MacOSDeleteCertificateCommandLineArgumentsFormat = "security delete-certificate -Z {0} {1}"; + private const string MacOSTrustCertificateCommandLine = "sudo"; +#if NETCOREAPP2_0 || NETCOREAPP2_1 + private static readonly string MacOSTrustCertificateCommandLineArguments = "security add-trusted-cert -d -r trustRoot -k " + MacOSSystemKeyChain + " "; +#endif + private const int UserCancelledErrorCode = 1223; + + public IList ListCertificates( + CertificatePurpose purpose, + StoreName storeName, + StoreLocation location, + bool isValid, + bool requireExportable = true) + { + var certificates = new List(); + try + { + using (var store = new X509Store(storeName, location)) + { + store.Open(OpenFlags.ReadOnly); + certificates.AddRange(store.Certificates.OfType()); + IEnumerable matchingCertificates = certificates; + switch (purpose) + { + case CertificatePurpose.All: + matchingCertificates = matchingCertificates + .Where(c => HasOid(c, AspNetHttpsOid) || HasOid(c, AspNetIdentityOid)); + break; + case CertificatePurpose.HTTPS: + matchingCertificates = matchingCertificates + .Where(c => HasOid(c, AspNetHttpsOid)); + break; + case CertificatePurpose.Signing: + matchingCertificates = matchingCertificates + .Where(c => HasOid(c, AspNetIdentityOid)); + break; + default: + break; + } + if (isValid) + { + // Ensure the certificate hasn't expired, has a private key and its exportable + // (for container/unix scenarios). + var now = DateTimeOffset.Now; + matchingCertificates = matchingCertificates + .Where(c => c.NotBefore <= now && + now <= c.NotAfter && + (!requireExportable || !RuntimeInformation.IsOSPlatform(OSPlatform.Windows) || IsExportable(c))); + } + + // We need to enumerate the certificates early to prevent dispoisng issues. + matchingCertificates = matchingCertificates.ToList(); + + var certificatesToDispose = certificates.Except(matchingCertificates); + DisposeCertificates(certificatesToDispose); + + store.Close(); + + return (IList)matchingCertificates; + } + } + catch + { + DisposeCertificates(certificates); + certificates.Clear(); + return certificates; + } + + bool HasOid(X509Certificate2 certificate, string oid) => + certificate.Extensions.OfType() + .Any(e => string.Equals(oid, e.Oid.Value, StringComparison.Ordinal)); +#if !XPLAT + bool IsExportable(X509Certificate2 c) => + ((c.GetRSAPrivateKey() is RSACryptoServiceProvider rsaPrivateKey && + rsaPrivateKey.CspKeyContainerInfo.Exportable) || + (c.GetRSAPrivateKey() is RSACng cngPrivateKey && + cngPrivateKey.Key.ExportPolicy == CngExportPolicies.AllowExport)); +#else + // Only check for RSA CryptoServiceProvider and do not fail in XPlat tooling as + // System.Security.Cryptography.Cng is not pat of the shared framework and we don't + // want to bring the dependency in on CLI scenarios. This functionality will be used + // on CLI scenarios as part of the first run experience, so checking the exportability + // of the certificate is not important. + bool IsExportable(X509Certificate2 c) => + ((c.GetRSAPrivateKey() is RSACryptoServiceProvider rsaPrivateKey && + rsaPrivateKey.CspKeyContainerInfo.Exportable) || !(c.GetRSAPrivateKey() is RSACryptoServiceProvider)); +#endif + } + + private void DisposeCertificates(IEnumerable disposables) + { + foreach (var disposable in disposables) + { + try + { + disposable.Dispose(); + } + catch + { + } + } + } + +#if NETCOREAPP2_0 || NETCOREAPP2_1 + + public X509Certificate2 CreateAspNetCoreHttpsDevelopmentCertificate(DateTimeOffset notBefore, DateTimeOffset notAfter, string subjectOverride) + { + var subject = new X500DistinguishedName(subjectOverride ?? LocalhostHttpsDistinguishedName); + var extensions = new List(); + var sanBuilder = new SubjectAlternativeNameBuilder(); + sanBuilder.AddDnsName(LocalhostHttpsDnsName); + + var keyUsage = new X509KeyUsageExtension(X509KeyUsageFlags.KeyEncipherment, critical: true); + var enhancedKeyUsage = new X509EnhancedKeyUsageExtension( + new OidCollection() { + new Oid( + ServerAuthenticationEnhancedKeyUsageOid, + ServerAuthenticationEnhancedKeyUsageOidFriendlyName) + }, + critical: true); + + var basicConstraints = new X509BasicConstraintsExtension( + certificateAuthority: false, + hasPathLengthConstraint: false, + pathLengthConstraint: 0, + critical: true); + + var aspNetHttpsExtension = new X509Extension( + new AsnEncodedData( + new Oid(AspNetHttpsOid, AspNetHttpsOidFriendlyName), + Encoding.ASCII.GetBytes(AspNetHttpsOidFriendlyName)), + critical: false); + + extensions.Add(basicConstraints); + extensions.Add(keyUsage); + extensions.Add(enhancedKeyUsage); + extensions.Add(sanBuilder.Build(critical: true)); + extensions.Add(aspNetHttpsExtension); + + var certificate = CreateSelfSignedCertificate(subject, extensions, notBefore, notAfter); + if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) + { + certificate.FriendlyName = AspNetHttpsOidFriendlyName; + } + + return certificate; + } + + public X509Certificate2 CreateApplicationTokenSigningDevelopmentCertificate(DateTimeOffset notBefore, DateTimeOffset notAfter, string subjectOverride) + { + var subject = new X500DistinguishedName(subjectOverride ?? IdentityDistinguishedName); + var extensions = new List(); + + var keyUsage = new X509KeyUsageExtension(X509KeyUsageFlags.DigitalSignature, critical: true); + var enhancedKeyUsage = new X509EnhancedKeyUsageExtension( + new OidCollection() { + new Oid( + ServerAuthenticationEnhancedKeyUsageOid, + ServerAuthenticationEnhancedKeyUsageOidFriendlyName) + }, + critical: true); + + var basicConstraints = new X509BasicConstraintsExtension( + certificateAuthority: false, + hasPathLengthConstraint: false, + pathLengthConstraint: 0, + critical: true); + + var aspNetIdentityExtension = new X509Extension( + new AsnEncodedData( + new Oid(AspNetIdentityOid, AspNetIdentityOidFriendlyName), + Encoding.ASCII.GetBytes(AspNetIdentityOidFriendlyName)), + critical: false); + + extensions.Add(basicConstraints); + extensions.Add(keyUsage); + extensions.Add(enhancedKeyUsage); + extensions.Add(aspNetIdentityExtension); + + var certificate = CreateSelfSignedCertificate(subject, extensions, notBefore, notAfter); + if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) + { + certificate.FriendlyName = AspNetIdentityOidFriendlyName; + } + + return certificate; + } + + public X509Certificate2 CreateSelfSignedCertificate( + X500DistinguishedName subject, + IEnumerable extensions, + DateTimeOffset notBefore, + DateTimeOffset notAfter) + { + var key = CreateKeyMaterial(RSAMinimumKeySizeInBits); + + var request = new CertificateRequest(subject, key, HashAlgorithmName.SHA256, RSASignaturePadding.Pkcs1); + foreach (var extension in extensions) + { + request.CertificateExtensions.Add(extension); + } + + return request.CreateSelfSigned(notBefore, notAfter); + + RSA CreateKeyMaterial(int minimumKeySize) + { + var rsa = RSA.Create(minimumKeySize); + if (rsa.KeySize < minimumKeySize) + { + throw new InvalidOperationException($"Failed to create a key with a size of {minimumKeySize} bits"); + } + + return rsa; + } + } + + public X509Certificate2 SaveCertificateInStore(X509Certificate2 certificate, StoreName name, StoreLocation location) + { + var imported = certificate; + if (!RuntimeInformation.IsOSPlatform(OSPlatform.OSX)) + { + // On non OSX systems we need to export the certificate and import it so that the transient + // key that we generated gets persisted. + var export = certificate.Export(X509ContentType.Pkcs12, ""); + imported = new X509Certificate2(export, "", X509KeyStorageFlags.PersistKeySet | X509KeyStorageFlags.Exportable); + Array.Clear(export, 0, export.Length); + } + + if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) + { + imported.FriendlyName = certificate.FriendlyName; + } + + using (var store = new X509Store(name, location)) + { + store.Open(OpenFlags.ReadWrite); + store.Add(imported); + store.Close(); + }; + + return imported; + } + + public void ExportCertificate(X509Certificate2 certificate, string path, bool includePrivateKey, string password) + { + if (Path.GetDirectoryName(path) != "") + { + Directory.CreateDirectory(Path.GetDirectoryName(path)); + } + + if (includePrivateKey) + { + var bytes = certificate.Export(X509ContentType.Pkcs12, password); + try + { + File.WriteAllBytes(path, bytes); + } + finally + { + Array.Clear(bytes, 0, bytes.Length); + } + } + else + { + var bytes = certificate.Export(X509ContentType.Cert); + File.WriteAllBytes(path, bytes); + } + } + + public void TrustCertificate(X509Certificate2 certificate) + { + // Strip certificate of the private key if any. + var publicCertificate = new X509Certificate2(certificate.Export(X509ContentType.Cert)); + + if (!IsTrusted(publicCertificate)) + { + if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) + { + TrustCertificateOnWindows(certificate, publicCertificate); + } + else if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX)) + { + TrustCertificateOnMac(publicCertificate); + } + } + } + + private void TrustCertificateOnMac(X509Certificate2 publicCertificate) + { + var tmpFile = Path.GetTempFileName(); + try + { + ExportCertificate(publicCertificate, tmpFile, includePrivateKey: false, password: null); + using (var process = Process.Start(MacOSTrustCertificateCommandLine, MacOSTrustCertificateCommandLineArguments + tmpFile)) + { + process.WaitForExit(); + if (process.ExitCode != 0) + { + throw new InvalidOperationException("There was an error trusting the certificate."); + } + } + } + finally + { + try + { + if (File.Exists(tmpFile)) + { + File.Delete(tmpFile); + } + } + catch + { + // We don't care if we can't delete the temp file. + } + } + } + + private static void TrustCertificateOnWindows(X509Certificate2 certificate, X509Certificate2 publicCertificate) + { + publicCertificate.FriendlyName = certificate.FriendlyName; + + using (var store = new X509Store(StoreName.Root, StoreLocation.CurrentUser)) + { + store.Open(OpenFlags.ReadWrite); + try + { + store.Add(publicCertificate); + } + catch (CryptographicException exception) when (exception.HResult == UserCancelledErrorCode) + { + throw new UserCancelledTrustException(); + } + store.Close(); + }; + } + + public bool IsTrusted(X509Certificate2 certificate) + { + if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) + { + return ListCertificates(CertificatePurpose.HTTPS, StoreName.Root, StoreLocation.CurrentUser, isValid: true, requireExportable: false) + .Any(c => c.Thumbprint == certificate.Thumbprint); + } + else if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX)) + { + var subjectMatch = Regex.Match(certificate.Subject, CertificateSubjectRegex, RegexOptions.Singleline, MaxRegexTimeout); + if (!subjectMatch.Success) + { + throw new InvalidOperationException($"Can't determine the subject for the certificate with subject '{certificate.Subject}'."); + } + var subject = subjectMatch.Groups[1].Value; + using (var checkTrustProcess = Process.Start(new ProcessStartInfo( + MacOSFindCertificateCommandLine, + string.Format(MacOSFindCertificateCommandLineArgumentsFormat, subject)) + { + RedirectStandardOutput = true + })) + { + var output = checkTrustProcess.StandardOutput.ReadToEnd(); + checkTrustProcess.WaitForExit(); + var matches = Regex.Matches(output, MacOSFindCertificateOutputRegex, RegexOptions.Multiline, MaxRegexTimeout); + var hashes = matches.OfType().Select(m => m.Groups[1].Value).ToList(); + return hashes.Any(h => string.Equals(h, certificate.Thumbprint, StringComparison.Ordinal)); + } + } + else + { + return false; + } + } + + public void CleanupHttpsCertificates(string subject = LocalhostHttpsDistinguishedName) + { + CleanupCertificates(CertificatePurpose.HTTPS, subject); + } + + public void CleanupCertificates(CertificatePurpose purpose, string subject) + { + // On OS X we don't have a good way to manage trusted certificates in the system keychain + // so we do everything by invoking the native toolchain. + // This has some limitations, like for example not being able to identify our custom OID extension. For that + // matter, when we are cleaning up certificates on the machine, we start by removing the trusted certificates. + // To do this, we list the certificates that we can identify on the current user personal store and we invoke + // the native toolchain to remove them from the sytem keychain. Once we have removed the trusted certificates, + // we remove the certificates from the local user store to finish up the cleanup. + var certificates = ListCertificates(purpose, StoreName.My, StoreLocation.CurrentUser, isValid: false); + foreach (var certificate in certificates) + { + RemoveCertificate(certificate, RemoveLocations.All); + } + } + + public void RemoveAllCertificates(CertificatePurpose purpose, StoreName storeName, StoreLocation storeLocation, string subject = null) + { + var certificates = RuntimeInformation.IsOSPlatform(OSPlatform.OSX) ? + ListCertificates(purpose, StoreName.My, StoreLocation.CurrentUser, isValid: false) : + ListCertificates(purpose, storeName, storeLocation, isValid: false); + var certificatesWithName = subject == null ? certificates : certificates.Where(c => c.Subject == subject); + + var removeLocation = storeName == StoreName.My ? RemoveLocations.Local : RemoveLocations.Trusted; + + foreach (var certificate in certificates) + { + RemoveCertificate(certificate, removeLocation); + } + + DisposeCertificates(certificates); + } + + private void RemoveCertificate(X509Certificate2 certificate, RemoveLocations locations) + { + switch (locations) + { + case RemoveLocations.Undefined: + throw new InvalidOperationException($"'{nameof(RemoveLocations.Undefined)}' is not a valid location."); + case RemoveLocations.Local: + RemoveCertificateFromUserStore(certificate); + break; + case RemoveLocations.Trusted when !RuntimeInformation.IsOSPlatform(OSPlatform.Linux): + RemoveCertificateFromTrustedRoots(certificate); + break; + case RemoveLocations.All: + if (!RuntimeInformation.IsOSPlatform(OSPlatform.Linux)) + { + RemoveCertificateFromTrustedRoots(certificate); + } + RemoveCertificateFromUserStore(certificate); + break; + default: + throw new InvalidOperationException("Invalid location."); + } + } + + private static void RemoveCertificateFromUserStore(X509Certificate2 certificate) + { + using (var store = new X509Store(StoreName.My, StoreLocation.CurrentUser)) + { + store.Open(OpenFlags.ReadWrite); + var matching = store.Certificates + .OfType() + .Single(c => c.SerialNumber == certificate.SerialNumber); + + store.Remove(matching); + store.Close(); + } + } + + private void RemoveCertificateFromTrustedRoots(X509Certificate2 certificate) + { + if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) + { + using (var store = new X509Store(StoreName.Root, StoreLocation.CurrentUser)) + { + store.Open(OpenFlags.ReadWrite); + var matching = store.Certificates + .OfType() + .Single(c => c.SerialNumber == certificate.SerialNumber); + + store.Remove(matching); + store.Close(); + } + } + else + { + if (IsTrusted(certificate)) // On OSX this check just ensures its on the system keychain + { + try + { + RemoveCertificateTrustRule(certificate); + } + catch + { + // We don't care if we fail to remove the trust rule if + // for some reason the certificate became untrusted. + // The delete command will fail if the certificate is + // trusted. + } + RemoveCertificateFromKeyChain(MacOSSystemKeyChain, certificate); + } + } + } + + private static void RemoveCertificateTrustRule(X509Certificate2 certificate) + { + var certificatePath = Path.GetTempFileName(); + try + { + var certBytes = certificate.Export(X509ContentType.Cert); + File.WriteAllBytes(certificatePath, certBytes); + var processInfo = new ProcessStartInfo( + MacOSRemoveCertificateTrustCommandLine, + string.Format( + MacOSRemoveCertificateTrustCommandLineArgumentsFormat, + certificatePath + )); + using (var process = Process.Start(processInfo)) + { + process.WaitForExit(); + } + } + finally + { + try + { + if (File.Exists(certificatePath)) + { + File.Delete(certificatePath); + } + } + catch + { + // We don't care about failing to do clean-up on a temp file. + } + } + } + + private static void RemoveCertificateFromKeyChain(string keyChain, X509Certificate2 certificate) + { + var processInfo = new ProcessStartInfo( + MacOSDeleteCertificateCommandLine, + string.Format( + MacOSDeleteCertificateCommandLineArgumentsFormat, + certificate.Thumbprint.ToUpperInvariant(), + keyChain + )) + { + RedirectStandardOutput = true, + RedirectStandardError = true + }; + + using (var process = Process.Start(processInfo)) + { + var output = process.StandardOutput.ReadToEnd() + process.StandardError.ReadToEnd(); + process.WaitForExit(); + + if (process.ExitCode != 0) + { + throw new InvalidOperationException($@"There was an error removing the certificate with thumbprint '{certificate.Thumbprint}'. + +{output}"); + } + } + } + + public EnsureCertificateResult EnsureAspNetCoreHttpsDevelopmentCertificate( + DateTimeOffset notBefore, + DateTimeOffset notAfter, + string path = null, + bool trust = false, + bool includePrivateKey = false, + string password = null, + string subject = LocalhostHttpsDistinguishedName) + { + return EnsureValidCertificateExists(notBefore, notAfter, CertificatePurpose.HTTPS, path, trust, includePrivateKey, password, subject); + } + + public EnsureCertificateResult EnsureAspNetCoreApplicationTokensDevelopmentCertificate( + DateTimeOffset notBefore, + DateTimeOffset notAfter, + string path = null, + bool trust = false, + bool includePrivateKey = false, + string password = null, + string subject = IdentityDistinguishedName) + { + return EnsureValidCertificateExists(notBefore, notAfter, CertificatePurpose.Signing, path, trust, includePrivateKey, password, subject); + } + + public EnsureCertificateResult EnsureValidCertificateExists( + DateTimeOffset notBefore, + DateTimeOffset notAfter, + CertificatePurpose purpose, + string path = null, + bool trust = false, + bool includePrivateKey = false, + string password = null, + string subjectOverride = null) + { + if (purpose == CertificatePurpose.All) + { + throw new ArgumentException("The certificate must have a specific purpose."); + } + + var certificates = ListCertificates(purpose, StoreName.My, StoreLocation.CurrentUser, isValid: true).Concat( + ListCertificates(purpose, StoreName.My, StoreLocation.LocalMachine, isValid: true)); + + certificates = subjectOverride == null ? certificates : certificates.Where(c => c.Subject == subjectOverride); + + var result = EnsureCertificateResult.Succeeded; + + X509Certificate2 certificate = null; + if (certificates.Count() > 0) + { + certificate = certificates.FirstOrDefault(); + result = EnsureCertificateResult.ValidCertificatePresent; + } + else + { + try + { + switch (purpose) + { + case CertificatePurpose.All: + throw new InvalidOperationException("The certificate must have a specific purpose."); + case CertificatePurpose.HTTPS: + certificate = CreateAspNetCoreHttpsDevelopmentCertificate(notBefore, notAfter, subjectOverride); + break; + case CertificatePurpose.Signing: + certificate = CreateApplicationTokenSigningDevelopmentCertificate(notBefore, notAfter, subjectOverride); + break; + default: + throw new InvalidOperationException("The certificate must have a purpose."); + } + } + catch + { + return EnsureCertificateResult.ErrorCreatingTheCertificate; + } + + try + { + certificate = SaveCertificateInStore(certificate, StoreName.My, StoreLocation.CurrentUser); + } + catch + { + return EnsureCertificateResult.ErrorSavingTheCertificateIntoTheCurrentUserPersonalStore; + } + } + if (path != null) + { + try + { + ExportCertificate(certificate, path, includePrivateKey, password); + } + catch + { + return EnsureCertificateResult.ErrorExportingTheCertificate; + } + } + + if ((RuntimeInformation.IsOSPlatform(OSPlatform.Windows) || RuntimeInformation.IsOSPlatform(OSPlatform.OSX)) && trust) + { + try + { + TrustCertificate(certificate); + } + catch (UserCancelledTrustException) + { + return EnsureCertificateResult.UserCancelledTrustStep; + } + catch + { + return EnsureCertificateResult.FailedToTrustTheCertificate; + } + } + + return result; + } + + private class UserCancelledTrustException : Exception + { + } + + private enum RemoveLocations + { + Undefined, + Local, + Trusted, + All + } +#endif + } +} \ No newline at end of file diff --git a/src/Shared/CertificateGeneration/CertificatePurpose.cs b/src/Shared/CertificateGeneration/CertificatePurpose.cs new file mode 100644 index 0000000000..1ad1a6d79b --- /dev/null +++ b/src/Shared/CertificateGeneration/CertificatePurpose.cs @@ -0,0 +1,12 @@ +// 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. + +namespace Microsoft.AspNetCore.Certificates.Generation +{ + internal enum CertificatePurpose + { + All, + HTTPS, + Signing + } +} \ No newline at end of file diff --git a/src/Shared/CertificateGeneration/Directory.Build.props b/src/Shared/CertificateGeneration/Directory.Build.props new file mode 100644 index 0000000000..63355f6faf --- /dev/null +++ b/src/Shared/CertificateGeneration/Directory.Build.props @@ -0,0 +1,8 @@ + + + + + + Microsoft.AspNetCore.Certificates.Generation.Sources + + diff --git a/src/Shared/CertificateGeneration/EnsureCertificateResult.cs b/src/Shared/CertificateGeneration/EnsureCertificateResult.cs new file mode 100644 index 0000000000..d3c86ce05d --- /dev/null +++ b/src/Shared/CertificateGeneration/EnsureCertificateResult.cs @@ -0,0 +1,20 @@ +// 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. + +#if NETCOREAPP2_0 || NETCOREAPP2_1 + +namespace Microsoft.AspNetCore.Certificates.Generation +{ + internal enum EnsureCertificateResult + { + Succeeded = 1, + ValidCertificatePresent, + ErrorCreatingTheCertificate, + ErrorSavingTheCertificateIntoTheCurrentUserPersonalStore, + ErrorExportingTheCertificate, + FailedToTrustTheCertificate, + UserCancelledTrustStep + } +} + +#endif \ No newline at end of file diff --git a/src/Shared/ClosedGenericMatcher/ClosedGenericMatcher.cs b/src/Shared/ClosedGenericMatcher/ClosedGenericMatcher.cs new file mode 100644 index 0000000000..f234c2edbc --- /dev/null +++ b/src/Shared/ClosedGenericMatcher/ClosedGenericMatcher.cs @@ -0,0 +1,106 @@ +// 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.Linq; +using System.Reflection; + +namespace Microsoft.Extensions.Internal +{ + /// + /// Helper related to generic interface definitions and implementing classes. + /// + internal static class ClosedGenericMatcher + { + /// + /// Determine whether is or implements a closed generic + /// created from . + /// + /// The of interest. + /// The open generic to match. Usually an interface. + /// + /// The closed generic created from that + /// is or implements. null if the two s have no such + /// relationship. + /// + /// + /// This method will return if is + /// typeof(KeyValuePair{,}), and is + /// typeof(KeyValuePair{string, object}). + /// + public static Type ExtractGenericInterface(Type queryType, Type interfaceType) + { + if (queryType == null) + { + throw new ArgumentNullException(nameof(queryType)); + } + + if (interfaceType == null) + { + throw new ArgumentNullException(nameof(interfaceType)); + } + + if (IsGenericInstantiation(queryType, interfaceType)) + { + // queryType matches (i.e. is a closed generic type created from) the open generic type. + return queryType; + } + + // Otherwise check all interfaces the type implements for a match. + // - If multiple different generic instantiations exists, we want the most derived one. + // - If that doesn't break the tie, then we sort alphabetically so that it's deterministic. + // + // We do this by looking at interfaces on the type, and recursing to the base type + // if we don't find any matches. + return GetGenericInstantiation(queryType, interfaceType); + } + + private static bool IsGenericInstantiation(Type candidate, Type interfaceType) + { + return + candidate.GetTypeInfo().IsGenericType && + candidate.GetGenericTypeDefinition() == interfaceType; + } + + private static Type GetGenericInstantiation(Type queryType, Type interfaceType) + { + Type bestMatch = null; + var interfaces = queryType.GetInterfaces(); + foreach (var @interface in interfaces) + { + if (IsGenericInstantiation(@interface, interfaceType)) + { + if (bestMatch == null) + { + bestMatch = @interface; + } + else if (StringComparer.Ordinal.Compare(@interface.FullName, bestMatch.FullName) < 0) + { + bestMatch = @interface; + } + else + { + // There are two matches at this level of the class hierarchy, but @interface is after + // bestMatch in the sort order. + } + } + } + + if (bestMatch != null) + { + return bestMatch; + } + + // BaseType will be null for object and interfaces, which means we've reached 'bottom'. + var baseType = queryType?.GetTypeInfo().BaseType; + if (baseType == null) + { + return null; + } + else + { + return GetGenericInstantiation(baseType, interfaceType); + } + } + } +} \ No newline at end of file diff --git a/src/Shared/CommandLineUtils/CommandLine/AnsiConsole.cs b/src/Shared/CommandLineUtils/CommandLine/AnsiConsole.cs new file mode 100644 index 0000000000..379235f274 --- /dev/null +++ b/src/Shared/CommandLineUtils/CommandLine/AnsiConsole.cs @@ -0,0 +1,143 @@ +// 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.IO; + +namespace Microsoft.Extensions.CommandLineUtils +{ + internal class AnsiConsole + { + private AnsiConsole(TextWriter writer, bool useConsoleColor) + { + Writer = writer; + + _useConsoleColor = useConsoleColor; + if (_useConsoleColor) + { + OriginalForegroundColor = Console.ForegroundColor; + } + } + + private int _boldRecursion; + private bool _useConsoleColor; + + public static AnsiConsole GetOutput(bool useConsoleColor) + { + return new AnsiConsole(Console.Out, useConsoleColor); + } + + public static AnsiConsole GetError(bool useConsoleColor) + { + return new AnsiConsole(Console.Error, useConsoleColor); + } + + public TextWriter Writer { get; } + + public ConsoleColor OriginalForegroundColor { get; } + + private void SetColor(ConsoleColor color) + { + Console.ForegroundColor = (ConsoleColor)(((int)Console.ForegroundColor & 0x08) | ((int)color & 0x07)); + } + + private void SetBold(bool bold) + { + _boldRecursion += bold ? 1 : -1; + if (_boldRecursion > 1 || (_boldRecursion == 1 && !bold)) + { + return; + } + + Console.ForegroundColor = (ConsoleColor)((int)Console.ForegroundColor ^ 0x08); + } + + public void WriteLine(string message) + { + if (!_useConsoleColor) + { + Writer.WriteLine(message); + return; + } + + var escapeScan = 0; + for (; ;) + { + var escapeIndex = message.IndexOf("\x1b[", escapeScan); + if (escapeIndex == -1) + { + var text = message.Substring(escapeScan); + Writer.Write(text); + break; + } + else + { + var startIndex = escapeIndex + 2; + var endIndex = startIndex; + while (endIndex != message.Length && + message[endIndex] >= 0x20 && + message[endIndex] <= 0x3f) + { + endIndex += 1; + } + + var text = message.Substring(escapeScan, escapeIndex - escapeScan); + Writer.Write(text); + if (endIndex == message.Length) + { + break; + } + + switch (message[endIndex]) + { + case 'm': + int value; + if (int.TryParse(message.Substring(startIndex, endIndex - startIndex), out value)) + { + switch (value) + { + case 1: + SetBold(true); + break; + case 22: + SetBold(false); + break; + case 30: + SetColor(ConsoleColor.Black); + break; + case 31: + SetColor(ConsoleColor.Red); + break; + case 32: + SetColor(ConsoleColor.Green); + break; + case 33: + SetColor(ConsoleColor.Yellow); + break; + case 34: + SetColor(ConsoleColor.Blue); + break; + case 35: + SetColor(ConsoleColor.Magenta); + break; + case 36: + SetColor(ConsoleColor.Cyan); + break; + case 37: + SetColor(ConsoleColor.Gray); + break; + case 39: + SetColor(OriginalForegroundColor); + break; + } + } + break; + } + + escapeScan = endIndex + 1; + } + } + Writer.WriteLine(); + } + } +} diff --git a/src/Shared/CommandLineUtils/CommandLine/CommandArgument.cs b/src/Shared/CommandLineUtils/CommandLine/CommandArgument.cs new file mode 100644 index 0000000000..4eac95982c --- /dev/null +++ b/src/Shared/CommandLineUtils/CommandLine/CommandArgument.cs @@ -0,0 +1,29 @@ +// 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.Collections.Generic; +using System.Linq; + +namespace Microsoft.Extensions.CommandLineUtils +{ + internal class CommandArgument + { + public CommandArgument() + { + Values = new List(); + } + + public string Name { get; set; } + public bool ShowInHelpText { get; set; } = true; + public string Description { get; set; } + public List Values { get; private set; } + public bool MultipleValues { get; set; } + public string Value + { + get + { + return Values.FirstOrDefault(); + } + } + } +} diff --git a/src/Shared/CommandLineUtils/CommandLine/CommandLineApplication.cs b/src/Shared/CommandLineUtils/CommandLine/CommandLineApplication.cs new file mode 100644 index 0000000000..b7c62b5a0b --- /dev/null +++ b/src/Shared/CommandLineUtils/CommandLine/CommandLineApplication.cs @@ -0,0 +1,555 @@ +// 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.Collections; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Microsoft.Extensions.CommandLineUtils +{ + internal class CommandLineApplication + { + // Indicates whether the parser should throw an exception when it runs into an unexpected argument. + // If this field is set to false, the parser will stop parsing when it sees an unexpected argument, and all + // remaining arguments, including the first unexpected argument, will be stored in RemainingArguments property. + private readonly bool _throwOnUnexpectedArg; + + public CommandLineApplication(bool throwOnUnexpectedArg = true) + { + _throwOnUnexpectedArg = throwOnUnexpectedArg; + Options = new List(); + Arguments = new List(); + Commands = new List(); + RemainingArguments = new List(); + Invoke = () => 0; + } + + public CommandLineApplication Parent { get; set; } + public string Name { get; set; } + public string FullName { get; set; } + public string Syntax { get; set; } + public string Description { get; set; } + public bool ShowInHelpText { get; set; } = true; + public string ExtendedHelpText { get; set; } + public readonly List Options; + public CommandOption OptionHelp { get; private set; } + public CommandOption OptionVersion { get; private set; } + public readonly List Arguments; + public readonly List RemainingArguments; + public bool IsShowingInformation { get; protected set; } // Is showing help or version? + public Func Invoke { get; set; } + public Func LongVersionGetter { get; set; } + public Func ShortVersionGetter { get; set; } + public readonly List Commands; + public bool AllowArgumentSeparator { get; set; } + public TextWriter Out { get; set; } = Console.Out; + public TextWriter Error { get; set; } = Console.Error; + + public IEnumerable GetOptions() + { + var expr = Options.AsEnumerable(); + var rootNode = this; + while (rootNode.Parent != null) + { + rootNode = rootNode.Parent; + expr = expr.Concat(rootNode.Options.Where(o => o.Inherited)); + } + + return expr; + } + + public CommandLineApplication Command(string name, Action configuration, + bool throwOnUnexpectedArg = true) + { + var command = new CommandLineApplication(throwOnUnexpectedArg) { Name = name, Parent = this }; + Commands.Add(command); + configuration(command); + return command; + } + + public CommandOption Option(string template, string description, CommandOptionType optionType) + => Option(template, description, optionType, _ => { }, inherited: false); + + public CommandOption Option(string template, string description, CommandOptionType optionType, bool inherited) + => Option(template, description, optionType, _ => { }, inherited); + + public CommandOption Option(string template, string description, CommandOptionType optionType, Action configuration) + => Option(template, description, optionType, configuration, inherited: false); + + public CommandOption Option(string template, string description, CommandOptionType optionType, Action configuration, bool inherited) + { + var option = new CommandOption(template, optionType) + { + Description = description, + Inherited = inherited + }; + Options.Add(option); + configuration(option); + return option; + } + + public CommandArgument Argument(string name, string description, bool multipleValues = false) + { + return Argument(name, description, _ => { }, multipleValues); + } + + public CommandArgument Argument(string name, string description, Action configuration, bool multipleValues = false) + { + var lastArg = Arguments.LastOrDefault(); + if (lastArg != null && lastArg.MultipleValues) + { + var message = string.Format("The last argument '{0}' accepts multiple values. No more argument can be added.", + lastArg.Name); + throw new InvalidOperationException(message); + } + + var argument = new CommandArgument { Name = name, Description = description, MultipleValues = multipleValues }; + Arguments.Add(argument); + configuration(argument); + return argument; + } + + public void OnExecute(Func invoke) + { + Invoke = invoke; + } + + public void OnExecute(Func> invoke) + { + Invoke = () => invoke().Result; + } + public int Execute(params string[] args) + { + CommandLineApplication command = this; + CommandOption option = null; + IEnumerator arguments = null; + + for (var index = 0; index < args.Length; index++) + { + var arg = args[index]; + var processed = false; + if (!processed && option == null) + { + string[] longOption = null; + string[] shortOption = null; + + if (arg.StartsWith("--")) + { + longOption = arg.Substring(2).Split(new[] { ':', '=' }, 2); + } + else if (arg.StartsWith("-")) + { + shortOption = arg.Substring(1).Split(new[] { ':', '=' }, 2); + } + if (longOption != null) + { + processed = true; + var longOptionName = longOption[0]; + option = command.GetOptions().SingleOrDefault(opt => string.Equals(opt.LongName, longOptionName, StringComparison.Ordinal)); + + if (option == null) + { + if (string.IsNullOrEmpty(longOptionName) && !command._throwOnUnexpectedArg && AllowArgumentSeparator) + { + // skip over the '--' argument separator + index++; + } + + HandleUnexpectedArg(command, args, index, argTypeName: "option"); + break; + } + + // If we find a help/version option, show information and stop parsing + if (command.OptionHelp == option) + { + command.ShowHelp(); + return 0; + } + else if (command.OptionVersion == option) + { + command.ShowVersion(); + return 0; + } + + if (longOption.Length == 2) + { + if (!option.TryParse(longOption[1])) + { + command.ShowHint(); + throw new CommandParsingException(command, $"Unexpected value '{longOption[1]}' for option '{option.LongName}'"); + } + option = null; + } + else if (option.OptionType == CommandOptionType.NoValue) + { + // No value is needed for this option + option.TryParse(null); + option = null; + } + } + if (shortOption != null) + { + processed = true; + option = command.GetOptions().SingleOrDefault(opt => string.Equals(opt.ShortName, shortOption[0], StringComparison.Ordinal)); + + // If not a short option, try symbol option + if (option == null) + { + option = command.GetOptions().SingleOrDefault(opt => string.Equals(opt.SymbolName, shortOption[0], StringComparison.Ordinal)); + } + + if (option == null) + { + HandleUnexpectedArg(command, args, index, argTypeName: "option"); + break; + } + + // If we find a help/version option, show information and stop parsing + if (command.OptionHelp == option) + { + command.ShowHelp(); + return 0; + } + else if (command.OptionVersion == option) + { + command.ShowVersion(); + return 0; + } + + if (shortOption.Length == 2) + { + if (!option.TryParse(shortOption[1])) + { + command.ShowHint(); + throw new CommandParsingException(command, $"Unexpected value '{shortOption[1]}' for option '{option.LongName}'"); + } + option = null; + } + else if (option.OptionType == CommandOptionType.NoValue) + { + // No value is needed for this option + option.TryParse(null); + option = null; + } + } + } + + if (!processed && option != null) + { + processed = true; + if (!option.TryParse(arg)) + { + command.ShowHint(); + throw new CommandParsingException(command, $"Unexpected value '{arg}' for option '{option.LongName}'"); + } + option = null; + } + + if (!processed && arguments == null) + { + var currentCommand = command; + foreach (var subcommand in command.Commands) + { + if (string.Equals(subcommand.Name, arg, StringComparison.OrdinalIgnoreCase)) + { + processed = true; + command = subcommand; + break; + } + } + + // If we detect a subcommand + if (command != currentCommand) + { + processed = true; + } + } + if (!processed) + { + if (arguments == null) + { + arguments = new CommandArgumentEnumerator(command.Arguments.GetEnumerator()); + } + if (arguments.MoveNext()) + { + processed = true; + arguments.Current.Values.Add(arg); + } + } + if (!processed) + { + HandleUnexpectedArg(command, args, index, argTypeName: "command or argument"); + break; + } + } + + if (option != null) + { + command.ShowHint(); + throw new CommandParsingException(command, $"Missing value for option '{option.LongName}'"); + } + + return command.Invoke(); + } + + // Helper method that adds a help option + public CommandOption HelpOption(string template) + { + // Help option is special because we stop parsing once we see it + // So we store it separately for further use + OptionHelp = Option(template, "Show help information", CommandOptionType.NoValue); + + return OptionHelp; + } + + public CommandOption VersionOption(string template, + string shortFormVersion, + string longFormVersion = null) + { + if (longFormVersion == null) + { + return VersionOption(template, () => shortFormVersion); + } + else + { + return VersionOption(template, () => shortFormVersion, () => longFormVersion); + } + } + + // Helper method that adds a version option + public CommandOption VersionOption(string template, + Func shortFormVersionGetter, + Func longFormVersionGetter = null) + { + // Version option is special because we stop parsing once we see it + // So we store it separately for further use + OptionVersion = Option(template, "Show version information", CommandOptionType.NoValue); + ShortVersionGetter = shortFormVersionGetter; + LongVersionGetter = longFormVersionGetter ?? shortFormVersionGetter; + + return OptionVersion; + } + + // Show short hint that reminds users to use help option + public void ShowHint() + { + if (OptionHelp != null) + { + Out.WriteLine(string.Format("Specify --{0} for a list of available options and commands.", OptionHelp.LongName)); + } + } + + // Show full help + public void ShowHelp(string commandName = null) + { + for (var cmd = this; cmd != null; cmd = cmd.Parent) + { + cmd.IsShowingInformation = true; + } + + Out.WriteLine(GetHelpText(commandName)); + } + + public virtual string GetHelpText(string commandName = null) + { + var headerBuilder = new StringBuilder("Usage:"); + for (var cmd = this; cmd != null; cmd = cmd.Parent) + { + headerBuilder.Insert(6, string.Format(" {0}", cmd.Name)); + } + + CommandLineApplication target; + + if (commandName == null || string.Equals(Name, commandName, StringComparison.OrdinalIgnoreCase)) + { + target = this; + } + else + { + target = Commands.SingleOrDefault(cmd => string.Equals(cmd.Name, commandName, StringComparison.OrdinalIgnoreCase)); + + if (target != null) + { + headerBuilder.AppendFormat(" {0}", commandName); + } + else + { + // The command name is invalid so don't try to show help for something that doesn't exist + target = this; + } + + } + + var optionsBuilder = new StringBuilder(); + var commandsBuilder = new StringBuilder(); + var argumentsBuilder = new StringBuilder(); + + var arguments = target.Arguments.Where(a => a.ShowInHelpText).ToList(); + if (arguments.Any()) + { + headerBuilder.Append(" [arguments]"); + + argumentsBuilder.AppendLine(); + argumentsBuilder.AppendLine("Arguments:"); + var maxArgLen = arguments.Max(a => a.Name.Length); + var outputFormat = string.Format(" {{0, -{0}}}{{1}}", maxArgLen + 2); + foreach (var arg in arguments) + { + argumentsBuilder.AppendFormat(outputFormat, arg.Name, arg.Description); + argumentsBuilder.AppendLine(); + } + } + + var options = target.GetOptions().Where(o => o.ShowInHelpText).ToList(); + if (options.Any()) + { + headerBuilder.Append(" [options]"); + + optionsBuilder.AppendLine(); + optionsBuilder.AppendLine("Options:"); + var maxOptLen = options.Max(o => o.Template.Length); + var outputFormat = string.Format(" {{0, -{0}}}{{1}}", maxOptLen + 2); + foreach (var opt in options) + { + optionsBuilder.AppendFormat(outputFormat, opt.Template, opt.Description); + optionsBuilder.AppendLine(); + } + } + + var commands = target.Commands.Where(c => c.ShowInHelpText).ToList(); + if (commands.Any()) + { + headerBuilder.Append(" [command]"); + + commandsBuilder.AppendLine(); + commandsBuilder.AppendLine("Commands:"); + var maxCmdLen = commands.Max(c => c.Name.Length); + var outputFormat = string.Format(" {{0, -{0}}}{{1}}", maxCmdLen + 2); + foreach (var cmd in commands.OrderBy(c => c.Name)) + { + commandsBuilder.AppendFormat(outputFormat, cmd.Name, cmd.Description); + commandsBuilder.AppendLine(); + } + + if (OptionHelp != null) + { + commandsBuilder.AppendLine(); + commandsBuilder.AppendFormat($"Use \"{target.Name} [command] --{OptionHelp.LongName}\" for more information about a command."); + commandsBuilder.AppendLine(); + } + } + + if (target.AllowArgumentSeparator) + { + headerBuilder.Append(" [[--] ...]"); + } + + headerBuilder.AppendLine(); + + var nameAndVersion = new StringBuilder(); + nameAndVersion.AppendLine(GetFullNameAndVersion()); + nameAndVersion.AppendLine(); + + return nameAndVersion.ToString() + + headerBuilder.ToString() + + argumentsBuilder.ToString() + + optionsBuilder.ToString() + + commandsBuilder.ToString() + + target.ExtendedHelpText; + } + + public void ShowVersion() + { + for (var cmd = this; cmd != null; cmd = cmd.Parent) + { + cmd.IsShowingInformation = true; + } + + Out.WriteLine(FullName); + Out.WriteLine(LongVersionGetter()); + } + + public string GetFullNameAndVersion() + { + return ShortVersionGetter == null ? FullName : string.Format("{0} {1}", FullName, ShortVersionGetter()); + } + + public void ShowRootCommandFullNameAndVersion() + { + var rootCmd = this; + while (rootCmd.Parent != null) + { + rootCmd = rootCmd.Parent; + } + + Out.WriteLine(rootCmd.GetFullNameAndVersion()); + Out.WriteLine(); + } + + private void HandleUnexpectedArg(CommandLineApplication command, string[] args, int index, string argTypeName) + { + if (command._throwOnUnexpectedArg) + { + command.ShowHint(); + throw new CommandParsingException(command, $"Unrecognized {argTypeName} '{args[index]}'"); + } + else + { + // All remaining arguments are stored for further use + command.RemainingArguments.AddRange(new ArraySegment(args, index, args.Length - index)); + } + } + + private class CommandArgumentEnumerator : IEnumerator + { + private readonly IEnumerator _enumerator; + + public CommandArgumentEnumerator(IEnumerator enumerator) + { + _enumerator = enumerator; + } + + public CommandArgument Current + { + get + { + return _enumerator.Current; + } + } + + object IEnumerator.Current + { + get + { + return Current; + } + } + + public void Dispose() + { + _enumerator.Dispose(); + } + + public bool MoveNext() + { + if (Current == null || !Current.MultipleValues) + { + return _enumerator.MoveNext(); + } + + // If current argument allows multiple values, we don't move forward and + // all later values will be added to current CommandArgument.Values + return true; + } + + public void Reset() + { + _enumerator.Reset(); + } + } + } +} diff --git a/src/Shared/CommandLineUtils/CommandLine/CommandOption.cs b/src/Shared/CommandLineUtils/CommandLine/CommandOption.cs new file mode 100644 index 0000000000..4e663773cc --- /dev/null +++ b/src/Shared/CommandLineUtils/CommandLine/CommandOption.cs @@ -0,0 +1,108 @@ +// 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.Collections.Generic; +using System.Linq; + +namespace Microsoft.Extensions.CommandLineUtils +{ + internal class CommandOption + { + public CommandOption(string template, CommandOptionType optionType) + { + Template = template; + OptionType = optionType; + Values = new List(); + + foreach (var part in Template.Split(new[] { ' ', '|' }, StringSplitOptions.RemoveEmptyEntries)) + { + if (part.StartsWith("--")) + { + LongName = part.Substring(2); + } + else if (part.StartsWith("-")) + { + var optName = part.Substring(1); + + // If there is only one char and it is not an English letter, it is a symbol option (e.g. "-?") + if (optName.Length == 1 && !IsEnglishLetter(optName[0])) + { + SymbolName = optName; + } + else + { + ShortName = optName; + } + } + else if (part.StartsWith("<") && part.EndsWith(">")) + { + ValueName = part.Substring(1, part.Length - 2); + } + else + { + throw new ArgumentException($"Invalid template pattern '{template}'", nameof(template)); + } + } + + if (string.IsNullOrEmpty(LongName) && string.IsNullOrEmpty(ShortName) && string.IsNullOrEmpty(SymbolName)) + { + throw new ArgumentException($"Invalid template pattern '{template}'", nameof(template)); + } + } + + public string Template { get; set; } + public string ShortName { get; set; } + public string LongName { get; set; } + public string SymbolName { get; set; } + public string ValueName { get; set; } + public string Description { get; set; } + public List Values { get; private set; } + public CommandOptionType OptionType { get; private set; } + public bool ShowInHelpText { get; set; } = true; + public bool Inherited { get; set; } + + public bool TryParse(string value) + { + switch (OptionType) + { + case CommandOptionType.MultipleValue: + Values.Add(value); + break; + case CommandOptionType.SingleValue: + if (Values.Any()) + { + return false; + } + Values.Add(value); + break; + case CommandOptionType.NoValue: + if (value != null) + { + return false; + } + // Add a value to indicate that this option was specified + Values.Add("on"); + break; + default: + break; + } + return true; + } + + public bool HasValue() + { + return Values.Any(); + } + + public string Value() + { + return HasValue() ? Values[0] : null; + } + + private bool IsEnglishLetter(char c) + { + return (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z'); + } + } +} diff --git a/src/Shared/CommandLineUtils/CommandLine/CommandOptionType.cs b/src/Shared/CommandLineUtils/CommandLine/CommandOptionType.cs new file mode 100644 index 0000000000..76fdf38f5e --- /dev/null +++ b/src/Shared/CommandLineUtils/CommandLine/CommandOptionType.cs @@ -0,0 +1,13 @@ +// 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. + + +namespace Microsoft.Extensions.CommandLineUtils +{ + internal enum CommandOptionType + { + MultipleValue, + SingleValue, + NoValue + } +} diff --git a/src/Shared/CommandLineUtils/CommandLine/CommandParsingException.cs b/src/Shared/CommandLineUtils/CommandLine/CommandParsingException.cs new file mode 100644 index 0000000000..2be62b87fa --- /dev/null +++ b/src/Shared/CommandLineUtils/CommandLine/CommandParsingException.cs @@ -0,0 +1,18 @@ +// 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; + +namespace Microsoft.Extensions.CommandLineUtils +{ + internal class CommandParsingException : Exception + { + public CommandParsingException(CommandLineApplication command, string message) + : base(message) + { + Command = command; + } + + public CommandLineApplication Command { get; } + } +} diff --git a/src/Shared/CommandLineUtils/Microsoft.Extensions.CommandLineUtils.Sources.shproj b/src/Shared/CommandLineUtils/Microsoft.Extensions.CommandLineUtils.Sources.shproj new file mode 100644 index 0000000000..c728fe1012 --- /dev/null +++ b/src/Shared/CommandLineUtils/Microsoft.Extensions.CommandLineUtils.Sources.shproj @@ -0,0 +1,13 @@ + + + + 00947d4a-c20e-46e3-90c3-6cd6bc87ee72 + 14.0 + + + + + + + + diff --git a/src/Shared/CommandLineUtils/Utilities/ArgumentEscaper.cs b/src/Shared/CommandLineUtils/Utilities/ArgumentEscaper.cs new file mode 100644 index 0000000000..7b696c5175 --- /dev/null +++ b/src/Shared/CommandLineUtils/Utilities/ArgumentEscaper.cs @@ -0,0 +1,109 @@ +// 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.Collections.Generic; +using System.Linq; +using System.Text; + +namespace Microsoft.Extensions.CommandLineUtils +{ + /// + /// A utility for escaping arguments for new processes. + /// + internal static class ArgumentEscaper + { + /// + /// Undo the processing which took place to create string[] args in Main, so that the next process will + /// receive the same string[] args. + /// + /// + /// See https://blogs.msdn.microsoft.com/twistylittlepassagesallalike/2011/04/23/everyone-quotes-command-line-arguments-the-wrong-way/ + /// + /// + /// + public static string EscapeAndConcatenate(IEnumerable args) + => string.Join(" ", args.Select(EscapeSingleArg)); + + private static string EscapeSingleArg(string arg) + { + var sb = new StringBuilder(); + + var needsQuotes = ShouldSurroundWithQuotes(arg); + var isQuoted = needsQuotes || IsSurroundedWithQuotes(arg); + + if (needsQuotes) + { + sb.Append('"'); + } + + for (int i = 0; i < arg.Length; ++i) + { + var backslashes = 0; + + // Consume all backslashes + while (i < arg.Length && arg[i] == '\\') + { + backslashes++; + i++; + } + + if (i == arg.Length && isQuoted) + { + // Escape any backslashes at the end of the arg when the argument is also quoted. + // This ensures the outside quote is interpreted as an argument delimiter + sb.Append('\\', 2 * backslashes); + } + else if (i == arg.Length) + { + // At then end of the arg, which isn't quoted, + // just add the backslashes, no need to escape + sb.Append('\\', backslashes); + } + else if (arg[i] == '"') + { + // Escape any preceding backslashes and the quote + sb.Append('\\', (2 * backslashes) + 1); + sb.Append('"'); + } + else + { + // Output any consumed backslashes and the character + sb.Append('\\', backslashes); + sb.Append(arg[i]); + } + } + + if (needsQuotes) + { + sb.Append('"'); + } + + return sb.ToString(); + } + + private static bool ShouldSurroundWithQuotes(string argument) + { + // Don't quote already quoted strings + if (IsSurroundedWithQuotes(argument)) + { + return false; + } + + // Only quote if whitespace exists in the string + return ContainsWhitespace(argument); + } + + private static bool IsSurroundedWithQuotes(string argument) + { + if (argument.Length <= 1) + { + return false; + } + + return argument[0] == '"' && argument[argument.Length - 1] == '"'; + } + + private static bool ContainsWhitespace(string argument) + => argument.IndexOfAny(new [] { ' ', '\t', '\n' }) >= 0; + } +} diff --git a/src/Shared/CommandLineUtils/Utilities/DotNetMuxer.cs b/src/Shared/CommandLineUtils/Utilities/DotNetMuxer.cs new file mode 100644 index 0000000000..52c98b5eb2 --- /dev/null +++ b/src/Shared/CommandLineUtils/Utilities/DotNetMuxer.cs @@ -0,0 +1,58 @@ +// 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. + +// System.AppContext.GetData is not available in these frameworks +#if !NET451 && !NET452 && !NET46 && !NET461 + +using System; +using System.Diagnostics; +using System.IO; +using System.Runtime.InteropServices; + +namespace Microsoft.Extensions.CommandLineUtils +{ + /// + /// Utilities for finding the "dotnet.exe" file from the currently running .NET Core application + /// + internal static class DotNetMuxer + { + private const string MuxerName = "dotnet"; + + static DotNetMuxer() + { + MuxerPath = TryFindMuxerPath(); + } + + /// + /// The full filepath to the .NET Core muxer. + /// + public static string MuxerPath { get; } + + /// + /// Finds the full filepath to the .NET Core muxer, + /// or returns a string containing the default name of the .NET Core muxer ('dotnet'). + /// + /// The path or a string named 'dotnet'. + public static string MuxerPathOrDefault() + => MuxerPath ?? MuxerName; + + private static string TryFindMuxerPath() + { + var fileName = MuxerName; + if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) + { + fileName += ".exe"; + } + + var mainModule = Process.GetCurrentProcess().MainModule; + if (!string.IsNullOrEmpty(mainModule?.FileName) + && Path.GetFileName(mainModule.FileName).Equals(fileName, StringComparison.OrdinalIgnoreCase)) + { + return mainModule.FileName; + } + + return null; + } + } +} +#endif diff --git a/src/Shared/CopyOnWriteDictionary/CopyOnWriteDictionary.cs b/src/Shared/CopyOnWriteDictionary/CopyOnWriteDictionary.cs new file mode 100644 index 0000000000..1408059ad9 --- /dev/null +++ b/src/Shared/CopyOnWriteDictionary/CopyOnWriteDictionary.cs @@ -0,0 +1,155 @@ +// 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.Collections; +using System.Collections.Generic; + +namespace Microsoft.Extensions.Internal +{ + internal class CopyOnWriteDictionary : IDictionary + { + private readonly IDictionary _sourceDictionary; + private readonly IEqualityComparer _comparer; + private IDictionary _innerDictionary; + + public CopyOnWriteDictionary( + IDictionary sourceDictionary, + IEqualityComparer comparer) + { + if (sourceDictionary == null) + { + throw new ArgumentNullException(nameof(sourceDictionary)); + } + + if (comparer == null) + { + throw new ArgumentNullException(nameof(comparer)); + } + + _sourceDictionary = sourceDictionary; + _comparer = comparer; + } + + private IDictionary ReadDictionary + { + get + { + return _innerDictionary ?? _sourceDictionary; + } + } + + private IDictionary WriteDictionary + { + get + { + if (_innerDictionary == null) + { + _innerDictionary = new Dictionary(_sourceDictionary, + _comparer); + } + + return _innerDictionary; + } + } + + public virtual ICollection Keys + { + get + { + return ReadDictionary.Keys; + } + } + + public virtual ICollection Values + { + get + { + return ReadDictionary.Values; + } + } + + public virtual int Count + { + get + { + return ReadDictionary.Count; + } + } + + public virtual bool IsReadOnly + { + get + { + return false; + } + } + + public virtual TValue this[TKey key] + { + get + { + return ReadDictionary[key]; + } + set + { + WriteDictionary[key] = value; + } + } + + public virtual bool ContainsKey(TKey key) + { + return ReadDictionary.ContainsKey(key); + } + + public virtual void Add(TKey key, TValue value) + { + WriteDictionary.Add(key, value); + } + + public virtual bool Remove(TKey key) + { + return WriteDictionary.Remove(key); + } + + public virtual bool TryGetValue(TKey key, out TValue value) + { + return ReadDictionary.TryGetValue(key, out value); + } + + public virtual void Add(KeyValuePair item) + { + WriteDictionary.Add(item); + } + + public virtual void Clear() + { + WriteDictionary.Clear(); + } + + public virtual bool Contains(KeyValuePair item) + { + return ReadDictionary.Contains(item); + } + + public virtual void CopyTo(KeyValuePair[] array, int arrayIndex) + { + ReadDictionary.CopyTo(array, arrayIndex); + } + + public bool Remove(KeyValuePair item) + { + return WriteDictionary.Remove(item); + } + + public virtual IEnumerator> GetEnumerator() + { + return ReadDictionary.GetEnumerator(); + } + + IEnumerator IEnumerable.GetEnumerator() + { + return GetEnumerator(); + } + } +} \ No newline at end of file diff --git a/src/Shared/CopyOnWriteDictionary/CopyOnWriteDictionaryHolder.cs b/src/Shared/CopyOnWriteDictionary/CopyOnWriteDictionaryHolder.cs new file mode 100644 index 0000000000..7cd935e940 --- /dev/null +++ b/src/Shared/CopyOnWriteDictionary/CopyOnWriteDictionaryHolder.cs @@ -0,0 +1,166 @@ +// 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.Collections.Generic; + +namespace Microsoft.Extensions.Internal +{ + internal struct CopyOnWriteDictionaryHolder + { + private readonly Dictionary _source; + private Dictionary _copy; + + public CopyOnWriteDictionaryHolder(Dictionary source) + { + if (source == null) + { + throw new ArgumentNullException(nameof(source)); + } + + _source = source; + _copy = null; + } + + public CopyOnWriteDictionaryHolder(CopyOnWriteDictionaryHolder source) + { + _source = source._copy ?? source._source; + _copy = null; + } + + public bool HasBeenCopied => _copy != null; + + public Dictionary ReadDictionary + { + get + { + if (_copy != null) + { + return _copy; + } + else if (_source != null) + { + return _source; + } + else + { + // Default-Constructor case + _copy = new Dictionary(); + return _copy; + } + } + } + + public Dictionary WriteDictionary + { + get + { + if (_copy == null && _source == null) + { + // Default-Constructor case + _copy = new Dictionary(); + } + else if (_copy == null) + { + _copy = new Dictionary(_source, _source.Comparer); + } + + return _copy; + } + } + + public Dictionary.KeyCollection Keys + { + get + { + return ReadDictionary.Keys; + } + } + + public Dictionary.ValueCollection Values + { + get + { + return ReadDictionary.Values; + } + } + + public int Count + { + get + { + return ReadDictionary.Count; + } + } + + public bool IsReadOnly + { + get + { + return false; + } + } + + public TValue this[TKey key] + { + get + { + return ReadDictionary[key]; + } + set + { + WriteDictionary[key] = value; + } + } + + public bool ContainsKey(TKey key) + { + return ReadDictionary.ContainsKey(key); + } + + public void Add(TKey key, TValue value) + { + WriteDictionary.Add(key, value); + } + + public bool Remove(TKey key) + { + return WriteDictionary.Remove(key); + } + + public bool TryGetValue(TKey key, out TValue value) + { + return ReadDictionary.TryGetValue(key, out value); + } + + public void Add(KeyValuePair item) + { + ((ICollection>)WriteDictionary).Add(item); + } + + public void Clear() + { + WriteDictionary.Clear(); + } + + public bool Contains(KeyValuePair item) + { + return ((ICollection>)ReadDictionary).Contains(item); + } + + public void CopyTo(KeyValuePair[] array, int arrayIndex) + { + ((ICollection>)ReadDictionary).CopyTo(array, arrayIndex); + } + + public bool Remove(KeyValuePair item) + { + return ((ICollection>)WriteDictionary).Remove(item); + } + + public Dictionary.Enumerator GetEnumerator() + { + return ReadDictionary.GetEnumerator(); + } + } +} diff --git a/src/Shared/HashCodeCombiner/HashCodeCombiner.cs b/src/Shared/HashCodeCombiner/HashCodeCombiner.cs new file mode 100644 index 0000000000..4df8b46b05 --- /dev/null +++ b/src/Shared/HashCodeCombiner/HashCodeCombiner.cs @@ -0,0 +1,84 @@ +// 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.Collections; +using System.Collections.Generic; +using System.Runtime.CompilerServices; + +namespace Microsoft.Extensions.Internal +{ + internal struct HashCodeCombiner + { + private long _combinedHash64; + + public int CombinedHash + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get { return _combinedHash64.GetHashCode(); } + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private HashCodeCombiner(long seed) + { + _combinedHash64 = seed; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void Add(IEnumerable e) + { + if (e == null) + { + Add(0); + } + else + { + var count = 0; + foreach (object o in e) + { + Add(o); + count++; + } + Add(count); + } + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static implicit operator int(HashCodeCombiner self) + { + return self.CombinedHash; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void Add(int i) + { + _combinedHash64 = ((_combinedHash64 << 5) + _combinedHash64) ^ i; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void Add(string s) + { + var hashCode = (s != null) ? s.GetHashCode() : 0; + Add(hashCode); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void Add(object o) + { + var hashCode = (o != null) ? o.GetHashCode() : 0; + Add(hashCode); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void Add(TValue value, IEqualityComparer comparer) + { + var hashCode = value != null ? comparer.GetHashCode(value) : 0; + Add(hashCode); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static HashCodeCombiner Start() + { + return new HashCodeCombiner(0x1505L); + } + } +} diff --git a/src/Shared/ObjectMethodExecutor/AwaitableInfo.cs b/src/Shared/ObjectMethodExecutor/AwaitableInfo.cs new file mode 100644 index 0000000000..431b83a6e5 --- /dev/null +++ b/src/Shared/ObjectMethodExecutor/AwaitableInfo.cs @@ -0,0 +1,127 @@ +// 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.Linq; +using System.Reflection; +using System.Runtime.CompilerServices; + +namespace Microsoft.Extensions.Internal +{ + internal struct AwaitableInfo + { + public Type AwaiterType { get; } + public PropertyInfo AwaiterIsCompletedProperty { get; } + public MethodInfo AwaiterGetResultMethod { get; } + public MethodInfo AwaiterOnCompletedMethod { get; } + public MethodInfo AwaiterUnsafeOnCompletedMethod { get; } + public Type ResultType { get; } + public MethodInfo GetAwaiterMethod { get; } + + public AwaitableInfo( + Type awaiterType, + PropertyInfo awaiterIsCompletedProperty, + MethodInfo awaiterGetResultMethod, + MethodInfo awaiterOnCompletedMethod, + MethodInfo awaiterUnsafeOnCompletedMethod, + Type resultType, + MethodInfo getAwaiterMethod) + { + AwaiterType = awaiterType; + AwaiterIsCompletedProperty = awaiterIsCompletedProperty; + AwaiterGetResultMethod = awaiterGetResultMethod; + AwaiterOnCompletedMethod = awaiterOnCompletedMethod; + AwaiterUnsafeOnCompletedMethod = awaiterUnsafeOnCompletedMethod; + ResultType = resultType; + GetAwaiterMethod = getAwaiterMethod; + } + + public static bool IsTypeAwaitable(Type type, out AwaitableInfo awaitableInfo) + { + // Based on Roslyn code: http://source.roslyn.io/#Microsoft.CodeAnalysis.Workspaces/Shared/Extensions/ISymbolExtensions.cs,db4d48ba694b9347 + + // Awaitable must have method matching "object GetAwaiter()" + var getAwaiterMethod = type.GetRuntimeMethods().FirstOrDefault(m => + m.Name.Equals("GetAwaiter", StringComparison.OrdinalIgnoreCase) + && m.GetParameters().Length == 0 + && m.ReturnType != null); + if (getAwaiterMethod == null) + { + awaitableInfo = default(AwaitableInfo); + return false; + } + + var awaiterType = getAwaiterMethod.ReturnType; + + // Awaiter must have property matching "bool IsCompleted { get; }" + var isCompletedProperty = awaiterType.GetRuntimeProperties().FirstOrDefault(p => + p.Name.Equals("IsCompleted", StringComparison.OrdinalIgnoreCase) + && p.PropertyType == typeof(bool) + && p.GetMethod != null); + if (isCompletedProperty == null) + { + awaitableInfo = default(AwaitableInfo); + return false; + } + + // Awaiter must implement INotifyCompletion + var awaiterInterfaces = awaiterType.GetInterfaces(); + var implementsINotifyCompletion = awaiterInterfaces.Any(t => t == typeof(INotifyCompletion)); + if (!implementsINotifyCompletion) + { + awaitableInfo = default(AwaitableInfo); + return false; + } + + // INotifyCompletion supplies a method matching "void OnCompleted(Action action)" + var iNotifyCompletionMap = awaiterType + .GetTypeInfo() + .GetRuntimeInterfaceMap(typeof(INotifyCompletion)); + var onCompletedMethod = iNotifyCompletionMap.InterfaceMethods.Single(m => + m.Name.Equals("OnCompleted", StringComparison.OrdinalIgnoreCase) + && m.ReturnType == typeof(void) + && m.GetParameters().Length == 1 + && m.GetParameters()[0].ParameterType == typeof(Action)); + + // Awaiter optionally implements ICriticalNotifyCompletion + var implementsICriticalNotifyCompletion = awaiterInterfaces.Any(t => t == typeof(ICriticalNotifyCompletion)); + MethodInfo unsafeOnCompletedMethod; + if (implementsICriticalNotifyCompletion) + { + // ICriticalNotifyCompletion supplies a method matching "void UnsafeOnCompleted(Action action)" + var iCriticalNotifyCompletionMap = awaiterType + .GetTypeInfo() + .GetRuntimeInterfaceMap(typeof(ICriticalNotifyCompletion)); + unsafeOnCompletedMethod = iCriticalNotifyCompletionMap.InterfaceMethods.Single(m => + m.Name.Equals("UnsafeOnCompleted", StringComparison.OrdinalIgnoreCase) + && m.ReturnType == typeof(void) + && m.GetParameters().Length == 1 + && m.GetParameters()[0].ParameterType == typeof(Action)); + } + else + { + unsafeOnCompletedMethod = null; + } + + // Awaiter must have method matching "void GetResult" or "T GetResult()" + var getResultMethod = awaiterType.GetRuntimeMethods().FirstOrDefault(m => + m.Name.Equals("GetResult") + && m.GetParameters().Length == 0); + if (getResultMethod == null) + { + awaitableInfo = default(AwaitableInfo); + return false; + } + + awaitableInfo = new AwaitableInfo( + awaiterType, + isCompletedProperty, + getResultMethod, + onCompletedMethod, + unsafeOnCompletedMethod, + getResultMethod.ReturnType, + getAwaiterMethod); + return true; + } + } +} diff --git a/src/Shared/ObjectMethodExecutor/CoercedAwaitableInfo.cs b/src/Shared/ObjectMethodExecutor/CoercedAwaitableInfo.cs new file mode 100644 index 0000000000..4e48ef09a1 --- /dev/null +++ b/src/Shared/ObjectMethodExecutor/CoercedAwaitableInfo.cs @@ -0,0 +1,55 @@ +// 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.Linq.Expressions; + +namespace Microsoft.Extensions.Internal +{ + internal struct CoercedAwaitableInfo + { + public AwaitableInfo AwaitableInfo { get; } + public Expression CoercerExpression { get; } + public Type CoercerResultType { get; } + public bool RequiresCoercion => CoercerExpression != null; + + public CoercedAwaitableInfo(AwaitableInfo awaitableInfo) + { + AwaitableInfo = awaitableInfo; + CoercerExpression = null; + CoercerResultType = null; + } + + public CoercedAwaitableInfo(Expression coercerExpression, Type coercerResultType, AwaitableInfo coercedAwaitableInfo) + { + CoercerExpression = coercerExpression; + CoercerResultType = coercerResultType; + AwaitableInfo = coercedAwaitableInfo; + } + + public static bool IsTypeAwaitable(Type type, out CoercedAwaitableInfo info) + { + if (AwaitableInfo.IsTypeAwaitable(type, out var directlyAwaitableInfo)) + { + info = new CoercedAwaitableInfo(directlyAwaitableInfo); + return true; + } + + // It's not directly awaitable, but maybe we can coerce it. + // Currently we support coercing FSharpAsync. + if (ObjectMethodExecutorFSharpSupport.TryBuildCoercerFromFSharpAsyncToAwaitable(type, + out var coercerExpression, + out var coercerResultType)) + { + if (AwaitableInfo.IsTypeAwaitable(coercerResultType, out var coercedAwaitableInfo)) + { + info = new CoercedAwaitableInfo(coercerExpression, coercerResultType, coercedAwaitableInfo); + return true; + } + } + + info = default(CoercedAwaitableInfo); + return false; + } + } +} diff --git a/src/Shared/ObjectMethodExecutor/ObjectMethodExecutor.cs b/src/Shared/ObjectMethodExecutor/ObjectMethodExecutor.cs new file mode 100644 index 0000000000..f8e5b70f0d --- /dev/null +++ b/src/Shared/ObjectMethodExecutor/ObjectMethodExecutor.cs @@ -0,0 +1,340 @@ +// 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.Collections.Generic; +using System.Linq.Expressions; +using System.Reflection; + +namespace Microsoft.Extensions.Internal +{ + internal class ObjectMethodExecutor + { + private readonly object[] _parameterDefaultValues; + private readonly MethodExecutorAsync _executorAsync; + private readonly MethodExecutor _executor; + + private static readonly ConstructorInfo _objectMethodExecutorAwaitableConstructor = + typeof(ObjectMethodExecutorAwaitable).GetConstructor(new[] { + typeof(object), // customAwaitable + typeof(Func), // getAwaiterMethod + typeof(Func), // isCompletedMethod + typeof(Func), // getResultMethod + typeof(Action), // onCompletedMethod + typeof(Action) // unsafeOnCompletedMethod + }); + + private ObjectMethodExecutor(MethodInfo methodInfo, TypeInfo targetTypeInfo, object[] parameterDefaultValues) + { + if (methodInfo == null) + { + throw new ArgumentNullException(nameof(methodInfo)); + } + + MethodInfo = methodInfo; + MethodParameters = methodInfo.GetParameters(); + TargetTypeInfo = targetTypeInfo; + MethodReturnType = methodInfo.ReturnType; + + var isAwaitable = CoercedAwaitableInfo.IsTypeAwaitable(MethodReturnType, out var coercedAwaitableInfo); + + IsMethodAsync = isAwaitable; + AsyncResultType = isAwaitable ? coercedAwaitableInfo.AwaitableInfo.ResultType : null; + + // Upstream code may prefer to use the sync-executor even for async methods, because if it knows + // that the result is a specific Task where T is known, then it can directly cast to that type + // and await it without the extra heap allocations involved in the _executorAsync code path. + _executor = GetExecutor(methodInfo, targetTypeInfo); + + if (IsMethodAsync) + { + _executorAsync = GetExecutorAsync(methodInfo, targetTypeInfo, coercedAwaitableInfo); + } + + _parameterDefaultValues = parameterDefaultValues; + } + + private delegate ObjectMethodExecutorAwaitable MethodExecutorAsync(object target, object[] parameters); + + private delegate object MethodExecutor(object target, object[] parameters); + + private delegate void VoidMethodExecutor(object target, object[] parameters); + + public MethodInfo MethodInfo { get; } + + public ParameterInfo[] MethodParameters { get; } + + public TypeInfo TargetTypeInfo { get; } + + public Type AsyncResultType { get; } + + // This field is made internal set because it is set in unit tests. + public Type MethodReturnType { get; internal set; } + + public bool IsMethodAsync { get; } + + public static ObjectMethodExecutor Create(MethodInfo methodInfo, TypeInfo targetTypeInfo) + { + return new ObjectMethodExecutor(methodInfo, targetTypeInfo, null); + } + + public static ObjectMethodExecutor Create(MethodInfo methodInfo, TypeInfo targetTypeInfo, object[] parameterDefaultValues) + { + if (parameterDefaultValues == null) + { + throw new ArgumentNullException(nameof(parameterDefaultValues)); + } + + return new ObjectMethodExecutor(methodInfo, targetTypeInfo, parameterDefaultValues); + } + + /// + /// Executes the configured method on . This can be used whether or not + /// the configured method is asynchronous. + /// + /// + /// Even if the target method is asynchronous, it's desirable to invoke it using Execute rather than + /// ExecuteAsync if you know at compile time what the return type is, because then you can directly + /// "await" that value (via a cast), and then the generated code will be able to reference the + /// resulting awaitable as a value-typed variable. If you use ExecuteAsync instead, the generated + /// code will have to treat the resulting awaitable as a boxed object, because it doesn't know at + /// compile time what type it would be. + /// + /// The object whose method is to be executed. + /// Parameters to pass to the method. + /// The method return value. + public object Execute(object target, object[] parameters) + { + return _executor(target, parameters); + } + + /// + /// Executes the configured method on . This can only be used if the configured + /// method is asynchronous. + /// + /// + /// If you don't know at compile time the type of the method's returned awaitable, you can use ExecuteAsync, + /// which supplies an awaitable-of-object. This always works, but can incur several extra heap allocations + /// as compared with using Execute and then using "await" on the result value typecasted to the known + /// awaitable type. The possible extra heap allocations are for: + /// + /// 1. The custom awaitable (though usually there's a heap allocation for this anyway, since normally + /// it's a reference type, and you normally create a new instance per call). + /// 2. The custom awaiter (whether or not it's a value type, since if it's not, you need a new instance + /// of it, and if it is, it will have to be boxed so the calling code can reference it as an object). + /// 3. The async result value, if it's a value type (it has to be boxed as an object, since the calling + /// code doesn't know what type it's going to be). + /// + /// The object whose method is to be executed. + /// Parameters to pass to the method. + /// An object that you can "await" to get the method return value. + public ObjectMethodExecutorAwaitable ExecuteAsync(object target, object[] parameters) + { + return _executorAsync(target, parameters); + } + + public object GetDefaultValueForParameter(int index) + { + if (_parameterDefaultValues == null) + { + throw new InvalidOperationException($"Cannot call {nameof(GetDefaultValueForParameter)}, because no parameter default values were supplied."); + } + + if (index < 0 || index > MethodParameters.Length - 1) + { + throw new ArgumentOutOfRangeException(nameof(index)); + } + + return _parameterDefaultValues[index]; + } + + private static MethodExecutor GetExecutor(MethodInfo methodInfo, TypeInfo targetTypeInfo) + { + // Parameters to executor + var targetParameter = Expression.Parameter(typeof(object), "target"); + var parametersParameter = Expression.Parameter(typeof(object[]), "parameters"); + + // Build parameter list + var parameters = new List(); + var paramInfos = methodInfo.GetParameters(); + for (int i = 0; i < paramInfos.Length; i++) + { + var paramInfo = paramInfos[i]; + var valueObj = Expression.ArrayIndex(parametersParameter, Expression.Constant(i)); + var valueCast = Expression.Convert(valueObj, paramInfo.ParameterType); + + // valueCast is "(Ti) parameters[i]" + parameters.Add(valueCast); + } + + // Call method + var instanceCast = Expression.Convert(targetParameter, targetTypeInfo.AsType()); + var methodCall = Expression.Call(instanceCast, methodInfo, parameters); + + // methodCall is "((Ttarget) target) method((T0) parameters[0], (T1) parameters[1], ...)" + // Create function + if (methodCall.Type == typeof(void)) + { + var lambda = Expression.Lambda(methodCall, targetParameter, parametersParameter); + var voidExecutor = lambda.Compile(); + return WrapVoidMethod(voidExecutor); + } + else + { + // must coerce methodCall to match ActionExecutor signature + var castMethodCall = Expression.Convert(methodCall, typeof(object)); + var lambda = Expression.Lambda(castMethodCall, targetParameter, parametersParameter); + return lambda.Compile(); + } + } + + private static MethodExecutor WrapVoidMethod(VoidMethodExecutor executor) + { + return delegate (object target, object[] parameters) + { + executor(target, parameters); + return null; + }; + } + + private static MethodExecutorAsync GetExecutorAsync( + MethodInfo methodInfo, + TypeInfo targetTypeInfo, + CoercedAwaitableInfo coercedAwaitableInfo) + { + // Parameters to executor + var targetParameter = Expression.Parameter(typeof(object), "target"); + var parametersParameter = Expression.Parameter(typeof(object[]), "parameters"); + + // Build parameter list + var parameters = new List(); + var paramInfos = methodInfo.GetParameters(); + for (int i = 0; i < paramInfos.Length; i++) + { + var paramInfo = paramInfos[i]; + var valueObj = Expression.ArrayIndex(parametersParameter, Expression.Constant(i)); + var valueCast = Expression.Convert(valueObj, paramInfo.ParameterType); + + // valueCast is "(Ti) parameters[i]" + parameters.Add(valueCast); + } + + // Call method + var instanceCast = Expression.Convert(targetParameter, targetTypeInfo.AsType()); + var methodCall = Expression.Call(instanceCast, methodInfo, parameters); + + // Using the method return value, construct an ObjectMethodExecutorAwaitable based on + // the info we have about its implementation of the awaitable pattern. Note that all + // the funcs/actions we construct here are precompiled, so that only one instance of + // each is preserved throughout the lifetime of the ObjectMethodExecutor. + + // var getAwaiterFunc = (object awaitable) => + // (object)((CustomAwaitableType)awaitable).GetAwaiter(); + var customAwaitableParam = Expression.Parameter(typeof(object), "awaitable"); + var awaitableInfo = coercedAwaitableInfo.AwaitableInfo; + var postCoercionMethodReturnType = coercedAwaitableInfo.CoercerResultType ?? methodInfo.ReturnType; + var getAwaiterFunc = Expression.Lambda>( + Expression.Convert( + Expression.Call( + Expression.Convert(customAwaitableParam, postCoercionMethodReturnType), + awaitableInfo.GetAwaiterMethod), + typeof(object)), + customAwaitableParam).Compile(); + + // var isCompletedFunc = (object awaiter) => + // ((CustomAwaiterType)awaiter).IsCompleted; + var isCompletedParam = Expression.Parameter(typeof(object), "awaiter"); + var isCompletedFunc = Expression.Lambda>( + Expression.MakeMemberAccess( + Expression.Convert(isCompletedParam, awaitableInfo.AwaiterType), + awaitableInfo.AwaiterIsCompletedProperty), + isCompletedParam).Compile(); + + var getResultParam = Expression.Parameter(typeof(object), "awaiter"); + Func getResultFunc; + if (awaitableInfo.ResultType == typeof(void)) + { + // var getResultFunc = (object awaiter) => + // { + // ((CustomAwaiterType)awaiter).GetResult(); // We need to invoke this to surface any exceptions + // return (object)null; + // }; + getResultFunc = Expression.Lambda>( + Expression.Block( + Expression.Call( + Expression.Convert(getResultParam, awaitableInfo.AwaiterType), + awaitableInfo.AwaiterGetResultMethod), + Expression.Constant(null) + ), + getResultParam).Compile(); + } + else + { + // var getResultFunc = (object awaiter) => + // (object)((CustomAwaiterType)awaiter).GetResult(); + getResultFunc = Expression.Lambda>( + Expression.Convert( + Expression.Call( + Expression.Convert(getResultParam, awaitableInfo.AwaiterType), + awaitableInfo.AwaiterGetResultMethod), + typeof(object)), + getResultParam).Compile(); + } + + // var onCompletedFunc = (object awaiter, Action continuation) => { + // ((CustomAwaiterType)awaiter).OnCompleted(continuation); + // }; + var onCompletedParam1 = Expression.Parameter(typeof(object), "awaiter"); + var onCompletedParam2 = Expression.Parameter(typeof(Action), "continuation"); + var onCompletedFunc = Expression.Lambda>( + Expression.Call( + Expression.Convert(onCompletedParam1, awaitableInfo.AwaiterType), + awaitableInfo.AwaiterOnCompletedMethod, + onCompletedParam2), + onCompletedParam1, + onCompletedParam2).Compile(); + + Action unsafeOnCompletedFunc = null; + if (awaitableInfo.AwaiterUnsafeOnCompletedMethod != null) + { + // var unsafeOnCompletedFunc = (object awaiter, Action continuation) => { + // ((CustomAwaiterType)awaiter).UnsafeOnCompleted(continuation); + // }; + var unsafeOnCompletedParam1 = Expression.Parameter(typeof(object), "awaiter"); + var unsafeOnCompletedParam2 = Expression.Parameter(typeof(Action), "continuation"); + unsafeOnCompletedFunc = Expression.Lambda>( + Expression.Call( + Expression.Convert(unsafeOnCompletedParam1, awaitableInfo.AwaiterType), + awaitableInfo.AwaiterUnsafeOnCompletedMethod, + unsafeOnCompletedParam2), + unsafeOnCompletedParam1, + unsafeOnCompletedParam2).Compile(); + } + + // If we need to pass the method call result through a coercer function to get an + // awaitable, then do so. + var coercedMethodCall = coercedAwaitableInfo.RequiresCoercion + ? Expression.Invoke(coercedAwaitableInfo.CoercerExpression, methodCall) + : (Expression)methodCall; + + // return new ObjectMethodExecutorAwaitable( + // (object)coercedMethodCall, + // getAwaiterFunc, + // isCompletedFunc, + // getResultFunc, + // onCompletedFunc, + // unsafeOnCompletedFunc); + var returnValueExpression = Expression.New( + _objectMethodExecutorAwaitableConstructor, + Expression.Convert(coercedMethodCall, typeof(object)), + Expression.Constant(getAwaiterFunc), + Expression.Constant(isCompletedFunc), + Expression.Constant(getResultFunc), + Expression.Constant(onCompletedFunc), + Expression.Constant(unsafeOnCompletedFunc, typeof(Action))); + + var lambda = Expression.Lambda(returnValueExpression, targetParameter, parametersParameter); + return lambda.Compile(); + } + } +} diff --git a/src/Shared/ObjectMethodExecutor/ObjectMethodExecutorAwaitable.cs b/src/Shared/ObjectMethodExecutor/ObjectMethodExecutorAwaitable.cs new file mode 100644 index 0000000000..7509b86b2b --- /dev/null +++ b/src/Shared/ObjectMethodExecutor/ObjectMethodExecutorAwaitable.cs @@ -0,0 +1,114 @@ +// 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 Microsoft.Extensions.Internal +{ + /// + /// Provides a common awaitable structure that can + /// return, regardless of whether the underlying value is a System.Task, an FSharpAsync, or an + /// application-defined custom awaitable. + /// + internal struct ObjectMethodExecutorAwaitable + { + private readonly object _customAwaitable; + private readonly Func _getAwaiterMethod; + private readonly Func _isCompletedMethod; + private readonly Func _getResultMethod; + private readonly Action _onCompletedMethod; + private readonly Action _unsafeOnCompletedMethod; + + // Perf note: since we're requiring the customAwaitable to be supplied here as an object, + // this will trigger a further allocation if it was a value type (i.e., to box it). We can't + // fix this by making the customAwaitable type generic, because the calling code typically + // does not know the type of the awaitable/awaiter at compile-time anyway. + // + // However, we could fix it by not passing the customAwaitable here at all, and instead + // passing a func that maps directly from the target object (e.g., controller instance), + // target method (e.g., action method info), and params array to the custom awaiter in the + // GetAwaiter() method below. In effect, by delaying the actual method call until the + // upstream code calls GetAwaiter on this ObjectMethodExecutorAwaitable instance. + // This optimization is not currently implemented because: + // [1] It would make no difference when the awaitable was an object type, which is + // by far the most common scenario (e.g., System.Task). + // [2] It would be complex - we'd need some kind of object pool to track all the parameter + // arrays until we needed to use them in GetAwaiter(). + // We can reconsider this in the future if there's a need to optimize for ValueTask + // or other value-typed awaitables. + + public ObjectMethodExecutorAwaitable( + object customAwaitable, + Func getAwaiterMethod, + Func isCompletedMethod, + Func getResultMethod, + Action onCompletedMethod, + Action unsafeOnCompletedMethod) + { + _customAwaitable = customAwaitable; + _getAwaiterMethod = getAwaiterMethod; + _isCompletedMethod = isCompletedMethod; + _getResultMethod = getResultMethod; + _onCompletedMethod = onCompletedMethod; + _unsafeOnCompletedMethod = unsafeOnCompletedMethod; + } + + public Awaiter GetAwaiter() + { + var customAwaiter = _getAwaiterMethod(_customAwaitable); + return new Awaiter(customAwaiter, _isCompletedMethod, _getResultMethod, _onCompletedMethod, _unsafeOnCompletedMethod); + } + + public struct Awaiter : ICriticalNotifyCompletion + { + private readonly object _customAwaiter; + private readonly Func _isCompletedMethod; + private readonly Func _getResultMethod; + private readonly Action _onCompletedMethod; + private readonly Action _unsafeOnCompletedMethod; + + public Awaiter( + object customAwaiter, + Func isCompletedMethod, + Func getResultMethod, + Action onCompletedMethod, + Action unsafeOnCompletedMethod) + { + _customAwaiter = customAwaiter; + _isCompletedMethod = isCompletedMethod; + _getResultMethod = getResultMethod; + _onCompletedMethod = onCompletedMethod; + _unsafeOnCompletedMethod = unsafeOnCompletedMethod; + } + + public bool IsCompleted => _isCompletedMethod(_customAwaiter); + + public object GetResult() => _getResultMethod(_customAwaiter); + + public void OnCompleted(Action continuation) + { + _onCompletedMethod(_customAwaiter, continuation); + } + + public void UnsafeOnCompleted(Action continuation) + { + // If the underlying awaitable implements ICriticalNotifyCompletion, use its UnsafeOnCompleted. + // If not, fall back on using its OnCompleted. + // + // Why this is safe: + // - Implementing ICriticalNotifyCompletion is a way of saying the caller can choose whether it + // needs the execution context to be preserved (which it signals by calling OnCompleted), or + // that it doesn't (which it signals by calling UnsafeOnCompleted). Obviously it's faster *not* + // to preserve and restore the context, so we prefer that where possible. + // - If a caller doesn't need the execution context to be preserved and hence calls UnsafeOnCompleted, + // there's no harm in preserving it anyway - it's just a bit of wasted cost. That's what will happen + // if a caller sees that the proxy implements ICriticalNotifyCompletion but the proxy chooses to + // pass the call on to the underlying awaitable's OnCompleted method. + + var underlyingMethodToUse = _unsafeOnCompletedMethod ?? _onCompletedMethod; + underlyingMethodToUse(_customAwaiter, continuation); + } + } + } +} \ No newline at end of file diff --git a/src/Shared/ObjectMethodExecutor/ObjectMethodExecutorFSharpSupport.cs b/src/Shared/ObjectMethodExecutor/ObjectMethodExecutorFSharpSupport.cs new file mode 100644 index 0000000000..2198c0ce45 --- /dev/null +++ b/src/Shared/ObjectMethodExecutor/ObjectMethodExecutorFSharpSupport.cs @@ -0,0 +1,151 @@ +// 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.Linq; +using System.Linq.Expressions; +using System.Reflection; +using System.Threading; +using System.Threading.Tasks; + +namespace Microsoft.Extensions.Internal +{ + /// + /// Helper for detecting whether a given type is FSharpAsync`1, and if so, supplying + /// an for mapping instances of that type to a C# awaitable. + /// + /// + /// The main design goal here is to avoid taking a compile-time dependency on + /// FSharp.Core.dll, because non-F# applications wouldn't use it. So all the references + /// to FSharp types have to be constructed dynamically at runtime. + /// + internal static class ObjectMethodExecutorFSharpSupport + { + private static object _fsharpValuesCacheLock = new object(); + private static Assembly _fsharpCoreAssembly; + private static MethodInfo _fsharpAsyncStartAsTaskGenericMethod; + private static PropertyInfo _fsharpOptionOfTaskCreationOptionsNoneProperty; + private static PropertyInfo _fsharpOptionOfCancellationTokenNoneProperty; + + public static bool TryBuildCoercerFromFSharpAsyncToAwaitable( + Type possibleFSharpAsyncType, + out Expression coerceToAwaitableExpression, + out Type awaitableType) + { + var methodReturnGenericType = possibleFSharpAsyncType.IsGenericType + ? possibleFSharpAsyncType.GetGenericTypeDefinition() + : null; + + if (!IsFSharpAsyncOpenGenericType(methodReturnGenericType)) + { + coerceToAwaitableExpression = null; + awaitableType = null; + return false; + } + + var awaiterResultType = possibleFSharpAsyncType.GetGenericArguments().Single(); + awaitableType = typeof(Task<>).MakeGenericType(awaiterResultType); + + // coerceToAwaitableExpression = (object fsharpAsync) => + // { + // return (object)FSharpAsync.StartAsTask( + // (Microsoft.FSharp.Control.FSharpAsync)fsharpAsync, + // FSharpOption.None, + // FSharpOption.None); + // }; + var startAsTaskClosedMethod = _fsharpAsyncStartAsTaskGenericMethod + .MakeGenericMethod(awaiterResultType); + var coerceToAwaitableParam = Expression.Parameter(typeof(object)); + coerceToAwaitableExpression = Expression.Lambda( + Expression.Convert( + Expression.Call( + startAsTaskClosedMethod, + Expression.Convert(coerceToAwaitableParam, possibleFSharpAsyncType), + Expression.MakeMemberAccess(null, _fsharpOptionOfTaskCreationOptionsNoneProperty), + Expression.MakeMemberAccess(null, _fsharpOptionOfCancellationTokenNoneProperty)), + typeof(object)), + coerceToAwaitableParam); + + return true; + } + + private static bool IsFSharpAsyncOpenGenericType(Type possibleFSharpAsyncGenericType) + { + var typeFullName = possibleFSharpAsyncGenericType?.FullName; + if (!string.Equals(typeFullName, "Microsoft.FSharp.Control.FSharpAsync`1", StringComparison.Ordinal)) + { + return false; + } + + lock (_fsharpValuesCacheLock) + { + if (_fsharpCoreAssembly != null) + { + // Since we've already found the real FSharpAsync.Core assembly, we just have + // to check that the supplied FSharpAsync`1 type is the one from that assembly. + return possibleFSharpAsyncGenericType.Assembly == _fsharpCoreAssembly; + } + else + { + // We'll keep trying to find the FSharp types/values each time any type called + // FSharpAsync`1 is supplied. + return TryPopulateFSharpValueCaches(possibleFSharpAsyncGenericType); + } + } + } + + private static bool TryPopulateFSharpValueCaches(Type possibleFSharpAsyncGenericType) + { + var assembly = possibleFSharpAsyncGenericType.Assembly; + var fsharpOptionType = assembly.GetType("Microsoft.FSharp.Core.FSharpOption`1"); + var fsharpAsyncType = assembly.GetType("Microsoft.FSharp.Control.FSharpAsync"); + + if (fsharpOptionType == null || fsharpAsyncType == null) + { + return false; + } + + // Get a reference to FSharpOption.None + var fsharpOptionOfTaskCreationOptionsType = fsharpOptionType + .MakeGenericType(typeof(TaskCreationOptions)); + _fsharpOptionOfTaskCreationOptionsNoneProperty = fsharpOptionOfTaskCreationOptionsType + .GetTypeInfo() + .GetRuntimeProperty("None"); + + // Get a reference to FSharpOption.None + var fsharpOptionOfCancellationTokenType = fsharpOptionType + .MakeGenericType(typeof(CancellationToken)); + _fsharpOptionOfCancellationTokenNoneProperty = fsharpOptionOfCancellationTokenType + .GetTypeInfo() + .GetRuntimeProperty("None"); + + // Get a reference to FSharpAsync.StartAsTask<> + var fsharpAsyncMethods = fsharpAsyncType + .GetRuntimeMethods() + .Where(m => m.Name.Equals("StartAsTask", StringComparison.Ordinal)); + foreach (var candidateMethodInfo in fsharpAsyncMethods) + { + var parameters = candidateMethodInfo.GetParameters(); + if (parameters.Length == 3 + && TypesHaveSameIdentity(parameters[0].ParameterType, possibleFSharpAsyncGenericType) + && parameters[1].ParameterType == fsharpOptionOfTaskCreationOptionsType + && parameters[2].ParameterType == fsharpOptionOfCancellationTokenType) + { + // This really does look like the correct method (and hence assembly). + _fsharpAsyncStartAsTaskGenericMethod = candidateMethodInfo; + _fsharpCoreAssembly = assembly; + break; + } + } + + return _fsharpCoreAssembly != null; + } + + private static bool TypesHaveSameIdentity(Type type1, Type type2) + { + return type1.Assembly == type2.Assembly + && string.Equals(type1.Namespace, type2.Namespace, StringComparison.Ordinal) + && string.Equals(type1.Name, type2.Name, StringComparison.Ordinal); + } + } +} diff --git a/src/Shared/Process/ProcessHelper.cs b/src/Shared/Process/ProcessHelper.cs new file mode 100644 index 0000000000..cf42a7e3a7 --- /dev/null +++ b/src/Shared/Process/ProcessHelper.cs @@ -0,0 +1,113 @@ +// 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.Collections.Generic; +using System.Diagnostics; +using System.IO; +using System.Runtime.InteropServices; + +namespace Microsoft.Extensions.Internal +{ + internal static class ProcessExtensions + { + private static readonly bool _isWindows = RuntimeInformation.IsOSPlatform(OSPlatform.Windows); + private static readonly TimeSpan _defaultTimeout = TimeSpan.FromSeconds(30); + + public static void KillTree(this Process process) + { + process.KillTree(_defaultTimeout); + } + + public static void KillTree(this Process process, TimeSpan timeout) + { + string stdout; + if (_isWindows) + { + RunProcessAndWaitForExit( + "taskkill", + $"/T /F /PID {process.Id}", + timeout, + out stdout); + } + else + { + var children = new HashSet(); + GetAllChildIdsUnix(process.Id, children, timeout); + foreach (var childId in children) + { + KillProcessUnix(childId, timeout); + } + KillProcessUnix(process.Id, timeout); + } + } + + private static void GetAllChildIdsUnix(int parentId, ISet children, TimeSpan timeout) + { + string stdout; + var exitCode = RunProcessAndWaitForExit( + "pgrep", + $"-P {parentId}", + timeout, + out stdout); + + if (exitCode == 0 && !string.IsNullOrEmpty(stdout)) + { + using (var reader = new StringReader(stdout)) + { + while (true) + { + var text = reader.ReadLine(); + if (text == null) + { + return; + } + + int id; + if (int.TryParse(text, out id)) + { + children.Add(id); + // Recursively get the children + GetAllChildIdsUnix(id, children, timeout); + } + } + } + } + } + + private static void KillProcessUnix(int processId, TimeSpan timeout) + { + string stdout; + RunProcessAndWaitForExit( + "kill", + $"-TERM {processId}", + timeout, + out stdout); + } + + private static int RunProcessAndWaitForExit(string fileName, string arguments, TimeSpan timeout, out string stdout) + { + var startInfo = new ProcessStartInfo + { + FileName = fileName, + Arguments = arguments, + RedirectStandardOutput = true, + UseShellExecute = false + }; + + var process = Process.Start(startInfo); + + stdout = null; + if (process.WaitForExit((int)timeout.TotalMilliseconds)) + { + stdout = process.StandardOutput.ReadToEnd(); + } + else + { + process.Kill(); + } + + return process.ExitCode; + } + } +} diff --git a/src/Shared/PropertyActivator/PropertyActivator.cs b/src/Shared/PropertyActivator/PropertyActivator.cs new file mode 100644 index 0000000000..925f6a76ae --- /dev/null +++ b/src/Shared/PropertyActivator/PropertyActivator.cs @@ -0,0 +1,110 @@ +// 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.Linq; +using System.Reflection; + +namespace Microsoft.Extensions.Internal +{ + internal class PropertyActivator + { + private readonly Func _valueAccessor; + private readonly Action _fastPropertySetter; + + public PropertyActivator( + PropertyInfo propertyInfo, + Func valueAccessor) + { + if (propertyInfo == null) + { + throw new ArgumentNullException(nameof(propertyInfo)); + } + + if (valueAccessor == null) + { + throw new ArgumentNullException(nameof(valueAccessor)); + } + + PropertyInfo = propertyInfo; + _valueAccessor = valueAccessor; + _fastPropertySetter = PropertyHelper.MakeFastPropertySetter(propertyInfo); + } + + public PropertyInfo PropertyInfo { get; private set; } + + public object Activate(object instance, TContext context) + { + if (instance == null) + { + throw new ArgumentNullException(nameof(instance)); + } + + var value = _valueAccessor(context); + _fastPropertySetter(instance, value); + return value; + } + + public static PropertyActivator[] GetPropertiesToActivate( + Type type, + Type activateAttributeType, + Func> createActivateInfo) + { + if (type == null) + { + throw new ArgumentNullException(nameof(type)); + } + + if (activateAttributeType == null) + { + throw new ArgumentNullException(nameof(activateAttributeType)); + } + + if (createActivateInfo == null) + { + throw new ArgumentNullException(nameof(createActivateInfo)); + } + + return GetPropertiesToActivate(type, activateAttributeType, createActivateInfo, includeNonPublic: false); + } + + public static PropertyActivator[] GetPropertiesToActivate( + Type type, + Type activateAttributeType, + Func> createActivateInfo, + bool includeNonPublic) + { + if (type == null) + { + throw new ArgumentNullException(nameof(type)); + } + + if (activateAttributeType == null) + { + throw new ArgumentNullException(nameof(activateAttributeType)); + } + + if (createActivateInfo == null) + { + throw new ArgumentNullException(nameof(createActivateInfo)); + } + + var properties = type.GetRuntimeProperties() + .Where((property) => + { + return + property.IsDefined(activateAttributeType) && + property.GetIndexParameters().Length == 0 && + property.SetMethod != null && + !property.SetMethod.IsStatic; + }); + + if (!includeNonPublic) + { + properties = properties.Where(property => property.SetMethod.IsPublic); + } + + return properties.Select(createActivateInfo).ToArray(); + } + } +} \ No newline at end of file diff --git a/src/Shared/PropertyHelper/PropertyHelper.cs b/src/Shared/PropertyHelper/PropertyHelper.cs new file mode 100644 index 0000000000..27ba5661a4 --- /dev/null +++ b/src/Shared/PropertyHelper/PropertyHelper.cs @@ -0,0 +1,526 @@ +// 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.Collections.Concurrent; +using System.Collections.Generic; +using System.Diagnostics; +using System.Linq; +using System.Reflection; + +namespace Microsoft.Extensions.Internal +{ + internal class PropertyHelper + { + // Delegate type for a by-ref property getter + private delegate TValue ByRefFunc(ref TDeclaringType arg); + + private static readonly MethodInfo CallPropertyGetterOpenGenericMethod = + typeof(PropertyHelper).GetTypeInfo().GetDeclaredMethod(nameof(CallPropertyGetter)); + + private static readonly MethodInfo CallPropertyGetterByReferenceOpenGenericMethod = + typeof(PropertyHelper).GetTypeInfo().GetDeclaredMethod(nameof(CallPropertyGetterByReference)); + + private static readonly MethodInfo CallNullSafePropertyGetterOpenGenericMethod = + typeof(PropertyHelper).GetTypeInfo().GetDeclaredMethod(nameof(CallNullSafePropertyGetter)); + + private static readonly MethodInfo CallNullSafePropertyGetterByReferenceOpenGenericMethod = + typeof(PropertyHelper).GetTypeInfo().GetDeclaredMethod(nameof(CallNullSafePropertyGetterByReference)); + + private static readonly MethodInfo CallPropertySetterOpenGenericMethod = + typeof(PropertyHelper).GetTypeInfo().GetDeclaredMethod(nameof(CallPropertySetter)); + + // Using an array rather than IEnumerable, as target will be called on the hot path numerous times. + private static readonly ConcurrentDictionary PropertiesCache = + new ConcurrentDictionary(); + + private static readonly ConcurrentDictionary VisiblePropertiesCache = + new ConcurrentDictionary(); + + private Action _valueSetter; + private Func _valueGetter; + + /// + /// Initializes a fast . + /// This constructor does not cache the helper. For caching, use . + /// + public PropertyHelper(PropertyInfo property) + { + if (property == null) + { + throw new ArgumentNullException(nameof(property)); + } + + Property = property; + Name = property.Name; + } + + /// + /// Gets the backing . + /// + public PropertyInfo Property { get; } + + /// + /// Gets (or sets in derived types) the property name. + /// + public virtual string Name { get; protected set; } + + /// + /// Gets the property value getter. + /// + public Func ValueGetter + { + get + { + if (_valueGetter == null) + { + _valueGetter = MakeFastPropertyGetter(Property); + } + + return _valueGetter; + } + } + + /// + /// Gets the property value setter. + /// + public Action ValueSetter + { + get + { + if (_valueSetter == null) + { + _valueSetter = MakeFastPropertySetter(Property); + } + + return _valueSetter; + } + } + + /// + /// Returns the property value for the specified . + /// + /// The object whose property value will be returned. + /// The property value. + public object GetValue(object instance) + { + return ValueGetter(instance); + } + + /// + /// Sets the property value for the specified . + /// + /// The object whose property value will be set. + /// The property value. + public void SetValue(object instance, object value) + { + ValueSetter(instance, value); + } + + /// + /// Creates and caches fast property helpers that expose getters for every public get property on the + /// underlying type. + /// + /// The type info to extract property accessors for. + /// A cached array of all public properties of the specified type. + /// + public static PropertyHelper[] GetProperties(TypeInfo typeInfo) + { + return GetProperties(typeInfo.AsType()); + } + + /// + /// Creates and caches fast property helpers that expose getters for every public get property on the + /// specified type. + /// + /// The type to extract property accessors for. + /// A cached array of all public properties of the specified type. + /// + public static PropertyHelper[] GetProperties(Type type) + { + return GetProperties(type, CreateInstance, PropertiesCache); + } + + /// + /// + /// Creates and caches fast property helpers that expose getters for every non-hidden get property + /// on the specified type. + /// + /// + /// excludes properties defined on base types that have been + /// hidden by definitions using the new keyword. + /// + /// + /// The type info to extract property accessors for. + /// + /// A cached array of all public properties of the specified type. + /// + public static PropertyHelper[] GetVisibleProperties(TypeInfo typeInfo) + { + return GetVisibleProperties(typeInfo.AsType(), CreateInstance, PropertiesCache, VisiblePropertiesCache); + } + + /// + /// + /// Creates and caches fast property helpers that expose getters for every non-hidden get property + /// on the specified type. + /// + /// + /// excludes properties defined on base types that have been + /// hidden by definitions using the new keyword. + /// + /// + /// The type to extract property accessors for. + /// + /// A cached array of all public properties of the specified type. + /// + public static PropertyHelper[] GetVisibleProperties(Type type) + { + return GetVisibleProperties(type, CreateInstance, PropertiesCache, VisiblePropertiesCache); + } + + /// + /// Creates a single fast property getter. The result is not cached. + /// + /// propertyInfo to extract the getter for. + /// a fast getter. + /// + /// This method is more memory efficient than a dynamically compiled lambda, and about the + /// same speed. + /// + public static Func MakeFastPropertyGetter(PropertyInfo propertyInfo) + { + Debug.Assert(propertyInfo != null); + + return MakeFastPropertyGetter( + propertyInfo, + CallPropertyGetterOpenGenericMethod, + CallPropertyGetterByReferenceOpenGenericMethod); + } + + /// + /// Creates a single fast property getter which is safe for a null input object. The result is not cached. + /// + /// propertyInfo to extract the getter for. + /// a fast getter. + /// + /// This method is more memory efficient than a dynamically compiled lambda, and about the + /// same speed. + /// + public static Func MakeNullSafeFastPropertyGetter(PropertyInfo propertyInfo) + { + Debug.Assert(propertyInfo != null); + + return MakeFastPropertyGetter( + propertyInfo, + CallNullSafePropertyGetterOpenGenericMethod, + CallNullSafePropertyGetterByReferenceOpenGenericMethod); + } + + private static Func MakeFastPropertyGetter( + PropertyInfo propertyInfo, + MethodInfo propertyGetterWrapperMethod, + MethodInfo propertyGetterByRefWrapperMethod) + { + Debug.Assert(propertyInfo != null); + + // Must be a generic method with a Func<,> parameter + Debug.Assert(propertyGetterWrapperMethod != null); + Debug.Assert(propertyGetterWrapperMethod.IsGenericMethodDefinition); + Debug.Assert(propertyGetterWrapperMethod.GetParameters().Length == 2); + + // Must be a generic method with a ByRefFunc<,> parameter + Debug.Assert(propertyGetterByRefWrapperMethod != null); + Debug.Assert(propertyGetterByRefWrapperMethod.IsGenericMethodDefinition); + Debug.Assert(propertyGetterByRefWrapperMethod.GetParameters().Length == 2); + + var getMethod = propertyInfo.GetMethod; + Debug.Assert(getMethod != null); + Debug.Assert(!getMethod.IsStatic); + Debug.Assert(getMethod.GetParameters().Length == 0); + + // Instance methods in the CLR can be turned into static methods where the first parameter + // is open over "target". This parameter is always passed by reference, so we have a code + // path for value types and a code path for reference types. + if (getMethod.DeclaringType.GetTypeInfo().IsValueType) + { + // Create a delegate (ref TDeclaringType) -> TValue + return MakeFastPropertyGetter( + typeof(ByRefFunc<,>), + getMethod, + propertyGetterByRefWrapperMethod); + } + else + { + // Create a delegate TDeclaringType -> TValue + return MakeFastPropertyGetter( + typeof(Func<,>), + getMethod, + propertyGetterWrapperMethod); + } + } + + private static Func MakeFastPropertyGetter( + Type openGenericDelegateType, + MethodInfo propertyGetMethod, + MethodInfo openGenericWrapperMethod) + { + var typeInput = propertyGetMethod.DeclaringType; + var typeOutput = propertyGetMethod.ReturnType; + + var delegateType = openGenericDelegateType.MakeGenericType(typeInput, typeOutput); + var propertyGetterDelegate = propertyGetMethod.CreateDelegate(delegateType); + + var wrapperDelegateMethod = openGenericWrapperMethod.MakeGenericMethod(typeInput, typeOutput); + var accessorDelegate = wrapperDelegateMethod.CreateDelegate( + typeof(Func), + propertyGetterDelegate); + + return (Func)accessorDelegate; + } + + /// + /// Creates a single fast property setter for reference types. The result is not cached. + /// + /// propertyInfo to extract the setter for. + /// a fast getter. + /// + /// This method is more memory efficient than a dynamically compiled lambda, and about the + /// same speed. This only works for reference types. + /// + public static Action MakeFastPropertySetter(PropertyInfo propertyInfo) + { + Debug.Assert(propertyInfo != null); + Debug.Assert(!propertyInfo.DeclaringType.GetTypeInfo().IsValueType); + + var setMethod = propertyInfo.SetMethod; + Debug.Assert(setMethod != null); + Debug.Assert(!setMethod.IsStatic); + Debug.Assert(setMethod.ReturnType == typeof(void)); + var parameters = setMethod.GetParameters(); + Debug.Assert(parameters.Length == 1); + + // Instance methods in the CLR can be turned into static methods where the first parameter + // is open over "target". This parameter is always passed by reference, so we have a code + // path for value types and a code path for reference types. + var typeInput = setMethod.DeclaringType; + var parameterType = parameters[0].ParameterType; + + // Create a delegate TDeclaringType -> { TDeclaringType.Property = TValue; } + var propertySetterAsAction = + setMethod.CreateDelegate(typeof(Action<,>).MakeGenericType(typeInput, parameterType)); + var callPropertySetterClosedGenericMethod = + CallPropertySetterOpenGenericMethod.MakeGenericMethod(typeInput, parameterType); + var callPropertySetterDelegate = + callPropertySetterClosedGenericMethod.CreateDelegate( + typeof(Action), propertySetterAsAction); + + return (Action)callPropertySetterDelegate; + } + + /// + /// Given an object, adds each instance property with a public get method as a key and its + /// associated value to a dictionary. + /// + /// If the object is already an instance, then a copy + /// is returned. + /// + /// + /// The implementation of PropertyHelper will cache the property accessors per-type. This is + /// faster when the same type is used multiple times with ObjectToDictionary. + /// + public static IDictionary ObjectToDictionary(object value) + { + var dictionary = value as IDictionary; + if (dictionary != null) + { + return new Dictionary(dictionary, StringComparer.OrdinalIgnoreCase); + } + + dictionary = new Dictionary(StringComparer.OrdinalIgnoreCase); + + if (value != null) + { + foreach (var helper in GetProperties(value.GetType())) + { + dictionary[helper.Name] = helper.GetValue(value); + } + } + + return dictionary; + } + + private static PropertyHelper CreateInstance(PropertyInfo property) + { + return new PropertyHelper(property); + } + + // Called via reflection + private static object CallPropertyGetter( + Func getter, + object target) + { + return getter((TDeclaringType)target); + } + + // Called via reflection + private static object CallPropertyGetterByReference( + ByRefFunc getter, + object target) + { + var unboxed = (TDeclaringType)target; + return getter(ref unboxed); + } + + // Called via reflection + private static object CallNullSafePropertyGetter( + Func getter, + object target) + { + if (target == null) + { + return null; + } + + return getter((TDeclaringType)target); + } + + // Called via reflection + private static object CallNullSafePropertyGetterByReference( + ByRefFunc getter, + object target) + { + if (target == null) + { + return null; + } + + var unboxed = (TDeclaringType)target; + return getter(ref unboxed); + } + + private static void CallPropertySetter( + Action setter, + object target, + object value) + { + setter((TDeclaringType)target, (TValue)value); + } + + protected static PropertyHelper[] GetVisibleProperties( + Type type, + Func createPropertyHelper, + ConcurrentDictionary allPropertiesCache, + ConcurrentDictionary visiblePropertiesCache) + { + PropertyHelper[] result; + if (visiblePropertiesCache.TryGetValue(type, out result)) + { + return result; + } + + // The simple and common case, this is normal POCO object - no need to allocate. + var allPropertiesDefinedOnType = true; + var allProperties = GetProperties(type, createPropertyHelper, allPropertiesCache); + foreach (var propertyHelper in allProperties) + { + if (propertyHelper.Property.DeclaringType != type) + { + allPropertiesDefinedOnType = false; + break; + } + } + + if (allPropertiesDefinedOnType) + { + result = allProperties; + visiblePropertiesCache.TryAdd(type, result); + return result; + } + + // There's some inherited properties here, so we need to check for hiding via 'new'. + var filteredProperties = new List(allProperties.Length); + foreach (var propertyHelper in allProperties) + { + var declaringType = propertyHelper.Property.DeclaringType; + if (declaringType == type) + { + filteredProperties.Add(propertyHelper); + continue; + } + + // If this property was declared on a base type then look for the definition closest to the + // the type to see if we should include it. + var ignoreProperty = false; + + // Walk up the hierarchy until we find the type that actually declares this + // PropertyInfo. + var currentTypeInfo = type.GetTypeInfo(); + var declaringTypeInfo = declaringType.GetTypeInfo(); + while (currentTypeInfo != null && currentTypeInfo != declaringTypeInfo) + { + // We've found a 'more proximal' public definition + var declaredProperty = currentTypeInfo.GetDeclaredProperty(propertyHelper.Name); + if (declaredProperty != null) + { + ignoreProperty = true; + break; + } + + currentTypeInfo = currentTypeInfo.BaseType?.GetTypeInfo(); + } + + if (!ignoreProperty) + { + filteredProperties.Add(propertyHelper); + } + } + + result = filteredProperties.ToArray(); + visiblePropertiesCache.TryAdd(type, result); + return result; + } + + protected static PropertyHelper[] GetProperties( + Type type, + Func createPropertyHelper, + ConcurrentDictionary cache) + { + // Unwrap nullable types. This means Nullable.Value and Nullable.HasValue will not be + // part of the sequence of properties returned by this method. + type = Nullable.GetUnderlyingType(type) ?? type; + + PropertyHelper[] helpers; + if (!cache.TryGetValue(type, out helpers)) + { + // We avoid loading indexed properties using the Where statement. + var properties = type.GetRuntimeProperties().Where(IsInterestingProperty); + + var typeInfo = type.GetTypeInfo(); + if (typeInfo.IsInterface) + { + // Reflection does not return information about inherited properties on the interface itself. + properties = properties.Concat(typeInfo.ImplementedInterfaces.SelectMany( + interfaceType => interfaceType.GetRuntimeProperties().Where(IsInterestingProperty))); + } + + helpers = properties.Select(p => createPropertyHelper(p)).ToArray(); + cache.TryAdd(type, helpers); + } + + return helpers; + } + + // Indexed properties are not useful (or valid) for grabbing properties off an object. + private static bool IsInterestingProperty(PropertyInfo property) + { + // For improving application startup time, do not use GetIndexParameters() api early in this check as it + // creates a copy of parameter array and also we would like to check for the presence of a get method + // and short circuit asap. + return property.GetMethod != null && + property.GetMethod.IsPublic && + !property.GetMethod.IsStatic && + property.GetMethod.GetParameters().Length == 0; + } + } +} diff --git a/src/Shared/RazorViews/AttributeValue.cs b/src/Shared/RazorViews/AttributeValue.cs new file mode 100644 index 0000000000..7a066a7040 --- /dev/null +++ b/src/Shared/RazorViews/AttributeValue.cs @@ -0,0 +1,38 @@ +// 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; + +namespace Microsoft.Extensions.RazorViews +{ + internal class AttributeValue + { + public AttributeValue(string prefix, object value, bool literal) + { + Prefix = prefix; + Value = value; + Literal = literal; + } + + public string Prefix { get; } + + public object Value { get; } + + public bool Literal { get; } + + public static AttributeValue FromTuple(Tuple value) + { + return new AttributeValue(value.Item1, value.Item2, value.Item3); + } + + public static AttributeValue FromTuple(Tuple value) + { + return new AttributeValue(value.Item1, value.Item2, value.Item3); + } + + public static implicit operator AttributeValue(Tuple value) + { + return FromTuple(value); + } + } +} \ No newline at end of file diff --git a/src/Shared/RazorViews/BaseView.cs b/src/Shared/RazorViews/BaseView.cs new file mode 100644 index 0000000000..a171d8d1f2 --- /dev/null +++ b/src/Shared/RazorViews/BaseView.cs @@ -0,0 +1,279 @@ +// 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.Collections.Generic; +using System.Diagnostics; +using System.Globalization; +using System.IO; +using System.Linq; +using System.Text; +using System.Text.Encodings.Web; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Http; + +namespace Microsoft.Extensions.RazorViews +{ + /// + /// Infrastructure + /// + internal abstract class BaseView + { + private static readonly Encoding UTF8NoBOM = new UTF8Encoding(encoderShouldEmitUTF8Identifier: false, throwOnInvalidBytes: true); + private readonly Stack _textWriterStack = new Stack(); + + /// + /// The request context + /// + protected HttpContext Context { get; private set; } + + /// + /// The request + /// + protected HttpRequest Request { get; private set; } + + /// + /// The response + /// + protected HttpResponse Response { get; private set; } + + /// + /// The output stream + /// + protected TextWriter Output { get; private set; } + + /// + /// Html encoder used to encode content. + /// + protected HtmlEncoder HtmlEncoder { get; set; } = HtmlEncoder.Default; + + /// + /// Url encoder used to encode content. + /// + protected UrlEncoder UrlEncoder { get; set; } = UrlEncoder.Default; + + /// + /// JavaScript encoder used to encode content. + /// + protected JavaScriptEncoder JavaScriptEncoder { get; set; } = JavaScriptEncoder.Default; + + /// + /// Execute an individual request + /// + /// + public async Task ExecuteAsync(HttpContext context) + { + Context = context; + Request = Context.Request; + Response = Context.Response; + Output = new StreamWriter(Response.Body, UTF8NoBOM, 4096, leaveOpen: true); + await ExecuteAsync(); + Output.Dispose(); + } + + /// + /// Execute an individual request + /// + public abstract Task ExecuteAsync(); + + protected virtual void PushWriter(TextWriter writer) + { + if (writer == null) + { + throw new ArgumentNullException(nameof(writer)); + } + + _textWriterStack.Push(Output); + Output = writer; + } + + protected virtual TextWriter PopWriter() + { + Output = _textWriterStack.Pop(); + return Output; + } + + /// + /// Write the given value without HTML encoding directly to . + /// + /// The to write. + protected void WriteLiteral(object value) + { + WriteLiteral(Convert.ToString(value, CultureInfo.InvariantCulture)); + } + + /// + /// Write the given value without HTML encoding directly to . + /// + /// The to write. + protected void WriteLiteral(string value) + { + if (!string.IsNullOrEmpty(value)) + { + Output.Write(value); + } + } + + private List AttributeValues { get; set; } + + protected void WriteAttributeValue(string thingy, int startPostion, object value, int endValue, int dealyo, bool yesno) + { + if (AttributeValues == null) + { + AttributeValues = new List(); + } + + AttributeValues.Add(value.ToString()); + } + + private string AttributeEnding { get; set; } + + protected void BeginWriteAttribute(string name, string begining, int startPosition, string ending, int endPosition, int thingy) + { + Debug.Assert(string.IsNullOrEmpty(AttributeEnding)); + + Output.Write(begining); + AttributeEnding = ending; + } + + protected void EndWriteAttribute() + { + Debug.Assert(!string.IsNullOrEmpty(AttributeEnding)); + + var attributes = string.Join(" ", AttributeValues); + Output.Write(attributes); + AttributeValues = null; + + Output.Write(AttributeEnding); + AttributeEnding = null; + } + + /// + /// Writes the given attribute to the given writer + /// + /// The name of the attribute to write + /// The value of the prefix + /// The value of the suffix + /// The s to write. + protected void WriteAttribute( + string name, + string leader, + string trailer, + params AttributeValue[] values) + { + if (name == null) + { + throw new ArgumentNullException(nameof(name)); + } + + if (leader == null) + { + throw new ArgumentNullException(nameof(leader)); + } + + if (trailer == null) + { + throw new ArgumentNullException(nameof(trailer)); + } + + WriteLiteral(leader); + foreach (var value in values) + { + WriteLiteral(value.Prefix); + + // The special cases here are that the value we're writing might already be a string, or that the + // value might be a bool. If the value is the bool 'true' we want to write the attribute name + // instead of the string 'true'. If the value is the bool 'false' we don't want to write anything. + // Otherwise the value is another object (perhaps an HtmlString) and we'll ask it to format itself. + string stringValue; + if (value.Value is bool) + { + if ((bool)value.Value) + { + stringValue = name; + } + else + { + continue; + } + } + else + { + stringValue = value.Value as string; + } + + // Call the WriteTo(string) overload when possible + if (value.Literal && stringValue != null) + { + WriteLiteral(stringValue); + } + else if (value.Literal) + { + WriteLiteral(value.Value); + } + else if (stringValue != null) + { + Write(stringValue); + } + else + { + Write(value.Value); + } + } + WriteLiteral(trailer); + } + + /// + /// is invoked + /// + /// The to invoke + protected void Write(HelperResult result) + { + Write(result); + } + + /// + /// Writes the specified to . + /// + /// The to write. + /// + /// is invoked for types. + /// For all other types, the encoded result of is written to + /// . + /// + protected void Write(object value) + { + if (value is HelperResult helperResult) + { + helperResult.WriteTo(Output); + } + else + { + Write(Convert.ToString(value, CultureInfo.InvariantCulture)); + } + } + + /// + /// Writes the specified with HTML encoding to . + /// + /// The to write. + protected void Write(string value) + { + WriteLiteral(HtmlEncoder.Encode(value)); + } + + protected string HtmlEncodeAndReplaceLineBreaks(string input) + { + if (string.IsNullOrEmpty(input)) + { + return string.Empty; + } + + // Split on line breaks before passing it through the encoder. + return string.Join("
" + Environment.NewLine, + input.Split(new[] { "\r\n" }, StringSplitOptions.None) + .SelectMany(s => s.Split(new[] { '\r', '\n' }, StringSplitOptions.None)) + .Select(HtmlEncoder.Encode)); + } + } +} \ No newline at end of file diff --git a/src/Shared/RazorViews/HelperResult.cs b/src/Shared/RazorViews/HelperResult.cs new file mode 100644 index 0000000000..c79944aae6 --- /dev/null +++ b/src/Shared/RazorViews/HelperResult.cs @@ -0,0 +1,34 @@ +// 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.IO; + +namespace Microsoft.Extensions.RazorViews +{ + /// + /// Represents a deferred write operation in a . + /// + internal class HelperResult + { + /// + /// Creates a new instance of . + /// + /// The delegate to invoke when is called. + public HelperResult(Action action) + { + WriteAction = action; + } + + public Action WriteAction { get; } + + /// + /// Method invoked to produce content from the . + /// + /// The instance to write to. + public void WriteTo(TextWriter writer) + { + WriteAction(writer); + } + } +} \ No newline at end of file diff --git a/src/Shared/SecurityHelper/SecurityHelper.cs b/src/Shared/SecurityHelper/SecurityHelper.cs new file mode 100644 index 0000000000..408ef6b224 --- /dev/null +++ b/src/Shared/SecurityHelper/SecurityHelper.cs @@ -0,0 +1,40 @@ +// 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.Linq; +using System.Security.Claims; + +namespace Microsoft.Extensions.Internal +{ + /// + /// Helper code used when implementing authentication middleware + /// + internal static class SecurityHelper + { + /// + /// Add all ClaimsIdentities from an additional ClaimPrincipal to the ClaimsPrincipal + /// Merges a new claims principal, placing all new identities first, and eliminating + /// any empty unauthenticated identities from context.User + /// + /// The containing existing . + /// The containing to be added. + public static ClaimsPrincipal MergeUserPrincipal(ClaimsPrincipal existingPrincipal, ClaimsPrincipal additionalPrincipal) + { + var newPrincipal = new ClaimsPrincipal(); + + // New principal identities go first + if (additionalPrincipal != null) + { + newPrincipal.AddIdentities(additionalPrincipal.Identities); + } + + // Then add any existing non empty or authenticated identities + if (existingPrincipal != null) + { + newPrincipal.AddIdentities(existingPrincipal.Identities.Where(i => i.IsAuthenticated || i.Claims.Any())); + } + return newPrincipal; + } + } +} diff --git a/src/Shared/StackTrace/ExceptionDetails/ExceptionDetails.cs b/src/Shared/StackTrace/ExceptionDetails/ExceptionDetails.cs new file mode 100644 index 0000000000..8862611136 --- /dev/null +++ b/src/Shared/StackTrace/ExceptionDetails/ExceptionDetails.cs @@ -0,0 +1,29 @@ +// 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.Collections.Generic; + +namespace Microsoft.Extensions.StackTrace.Sources +{ + /// + /// Contains details for individual exception messages. + /// + internal class ExceptionDetails + { + /// + /// An individual exception + /// + public Exception Error { get; set; } + + /// + /// The generated stack frames + /// + public IEnumerable StackFrames { get; set; } + + /// + /// Gets or sets the summary message. + /// + public string ErrorMessage { get; set; } + } +} diff --git a/src/Shared/StackTrace/ExceptionDetails/ExceptionDetailsProvider.cs b/src/Shared/StackTrace/ExceptionDetails/ExceptionDetailsProvider.cs new file mode 100644 index 0000000000..2d1dd20710 --- /dev/null +++ b/src/Shared/StackTrace/ExceptionDetails/ExceptionDetailsProvider.cs @@ -0,0 +1,170 @@ +// 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.Collections.Generic; +using System.IO; +using System.Linq; +using System.Reflection; +using Microsoft.Extensions.FileProviders; + +namespace Microsoft.Extensions.StackTrace.Sources +{ + internal class ExceptionDetailsProvider + { + private readonly IFileProvider _fileProvider; + private readonly int _sourceCodeLineCount; + + public ExceptionDetailsProvider(IFileProvider fileProvider, int sourceCodeLineCount) + { + _fileProvider = fileProvider; + _sourceCodeLineCount = sourceCodeLineCount; + } + + public IEnumerable GetDetails(Exception exception) + { + var exceptions = FlattenAndReverseExceptionTree(exception); + + foreach (var ex in exceptions) + { + yield return new ExceptionDetails + { + Error = ex, + StackFrames = StackTraceHelper.GetFrames(ex) + .Select(frame => GetStackFrameSourceCodeInfo( + frame.MethodDisplayInfo.ToString(), + frame.FilePath, + frame.LineNumber)) + }; + } + } + + private static IEnumerable FlattenAndReverseExceptionTree(Exception ex) + { + // ReflectionTypeLoadException is special because the details are in + // the LoaderExceptions property + var typeLoadException = ex as ReflectionTypeLoadException; + if (typeLoadException != null) + { + var typeLoadExceptions = new List(); + foreach (var loadException in typeLoadException.LoaderExceptions) + { + typeLoadExceptions.AddRange(FlattenAndReverseExceptionTree(loadException)); + } + + typeLoadExceptions.Add(ex); + return typeLoadExceptions; + } + + var list = new List(); + if (ex is AggregateException aggregateException) + { + list.Add(ex); + foreach (var innerException in aggregateException.Flatten().InnerExceptions) + { + list.Add(innerException); + } + } + + else + { + while (ex != null) + { + list.Add(ex); + ex = ex.InnerException; + } + list.Reverse(); + } + + return list; + } + + // make it internal to enable unit testing + internal StackFrameSourceCodeInfo GetStackFrameSourceCodeInfo(string method, string filePath, int lineNumber) + { + var stackFrame = new StackFrameSourceCodeInfo + { + Function = method, + File = filePath, + Line = lineNumber + }; + + if (string.IsNullOrEmpty(stackFrame.File)) + { + return stackFrame; + } + + IEnumerable lines = null; + if (File.Exists(stackFrame.File)) + { + lines = File.ReadLines(stackFrame.File); + } + else + { + // Handle relative paths and embedded files + var fileInfo = _fileProvider.GetFileInfo(stackFrame.File); + if (fileInfo.Exists) + { + // ReadLines doesn't accept a stream. Use ReadLines as its more efficient + // relative to reading lines via stream reader + if (!string.IsNullOrEmpty(fileInfo.PhysicalPath)) + { + lines = File.ReadLines(fileInfo.PhysicalPath); + } + else + { + lines = ReadLines(fileInfo); + } + } + } + + if (lines != null) + { + ReadFrameContent(stackFrame, lines, stackFrame.Line, stackFrame.Line); + } + + return stackFrame; + } + + // make it internal to enable unit testing + internal void ReadFrameContent( + StackFrameSourceCodeInfo frame, + IEnumerable allLines, + int errorStartLineNumberInFile, + int errorEndLineNumberInFile) + { + // Get the line boundaries in the file to be read and read all these lines at once into an array. + var preErrorLineNumberInFile = Math.Max(errorStartLineNumberInFile - _sourceCodeLineCount, 1); + var postErrorLineNumberInFile = errorEndLineNumberInFile + _sourceCodeLineCount; + var codeBlock = allLines + .Skip(preErrorLineNumberInFile - 1) + .Take(postErrorLineNumberInFile - preErrorLineNumberInFile + 1) + .ToArray(); + + var numOfErrorLines = (errorEndLineNumberInFile - errorStartLineNumberInFile) + 1; + var errorStartLineNumberInArray = errorStartLineNumberInFile - preErrorLineNumberInFile; + + frame.PreContextLine = preErrorLineNumberInFile; + frame.PreContextCode = codeBlock.Take(errorStartLineNumberInArray).ToArray(); + frame.ContextCode = codeBlock + .Skip(errorStartLineNumberInArray) + .Take(numOfErrorLines) + .ToArray(); + frame.PostContextCode = codeBlock + .Skip(errorStartLineNumberInArray + numOfErrorLines) + .ToArray(); + } + + private static IEnumerable ReadLines(IFileInfo fileInfo) + { + using (var reader = new StreamReader(fileInfo.CreateReadStream())) + { + string line; + while ((line = reader.ReadLine()) != null) + { + yield return line; + } + } + } + } +} diff --git a/src/Shared/StackTrace/StackFrame/MethodDisplayInfo.cs b/src/Shared/StackTrace/StackFrame/MethodDisplayInfo.cs new file mode 100644 index 0000000000..b1c0ccc188 --- /dev/null +++ b/src/Shared/StackTrace/StackFrame/MethodDisplayInfo.cs @@ -0,0 +1,49 @@ +// 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.Collections.Generic; +using System.Linq; +using System.Text; + +namespace Microsoft.Extensions.StackTrace.Sources +{ + internal class MethodDisplayInfo + { + public string DeclaringTypeName { get; set; } + + public string Name { get; set; } + + public string GenericArguments { get; set; } + + public string SubMethod { get; set; } + + public IEnumerable Parameters { get; set; } + + public override string ToString() + { + var builder = new StringBuilder(); + if (!string.IsNullOrEmpty(DeclaringTypeName)) + { + builder + .Append(DeclaringTypeName) + .Append("."); + } + + builder.Append(Name); + builder.Append(GenericArguments); + + builder.Append("("); + builder.Append(string.Join(", ", Parameters.Select(p => p.ToString()))); + builder.Append(")"); + + if (!string.IsNullOrEmpty(SubMethod)) + { + builder.Append("+"); + builder.Append(SubMethod); + builder.Append("()"); + } + + return builder.ToString(); + } + } +} diff --git a/src/Shared/StackTrace/StackFrame/ParameterDisplayInfo.cs b/src/Shared/StackTrace/StackFrame/ParameterDisplayInfo.cs new file mode 100644 index 0000000000..1199a8386d --- /dev/null +++ b/src/Shared/StackTrace/StackFrame/ParameterDisplayInfo.cs @@ -0,0 +1,33 @@ +// 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.Text; + +namespace Microsoft.Extensions.StackTrace.Sources +{ + internal class ParameterDisplayInfo + { + public string Name { get; set; } + + public string Type { get; set; } + + public string Prefix { get; set; } + + public override string ToString() + { + var builder = new StringBuilder(); + if (!string.IsNullOrEmpty(Prefix)) + { + builder + .Append(Prefix) + .Append(" "); + } + + builder.Append(Type); + builder.Append(" "); + builder.Append(Name); + + return builder.ToString(); + } + } +} diff --git a/src/Shared/StackTrace/StackFrame/PortablePdbReader.cs b/src/Shared/StackTrace/StackFrame/PortablePdbReader.cs new file mode 100644 index 0000000000..ff6a4947f8 --- /dev/null +++ b/src/Shared/StackTrace/StackFrame/PortablePdbReader.cs @@ -0,0 +1,135 @@ +// 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.Collections.Generic; +using System.Diagnostics; +using System.IO; +using System.Reflection; +using System.Reflection.Metadata; +using System.Reflection.Metadata.Ecma335; +using System.Reflection.PortableExecutable; + +namespace Microsoft.Extensions.StackTrace.Sources +{ + internal class PortablePdbReader : IDisposable + { + private readonly Dictionary _cache = + new Dictionary(StringComparer.Ordinal); + + public void PopulateStackFrame(StackFrameInfo frameInfo, MethodBase method, int IlOffset) + { + if (method.Module.Assembly.IsDynamic) + { + return; + } + + var metadataReader = GetMetadataReader(method.Module.Assembly.Location); + + if (metadataReader == null) + { + return; + } + + var methodToken = MetadataTokens.Handle(method.MetadataToken); + + Debug.Assert(methodToken.Kind == HandleKind.MethodDefinition); + + var handle = ((MethodDefinitionHandle)methodToken).ToDebugInformationHandle(); + + if (!handle.IsNil) + { + var methodDebugInfo = metadataReader.GetMethodDebugInformation(handle); + var sequencePoints = methodDebugInfo.GetSequencePoints(); + SequencePoint? bestPointSoFar = null; + + foreach (var point in sequencePoints) + { + if (point.Offset > IlOffset) + { + break; + } + + if (point.StartLine != SequencePoint.HiddenLine) + { + bestPointSoFar = point; + } + } + + if (bestPointSoFar.HasValue) + { + frameInfo.LineNumber = bestPointSoFar.Value.StartLine; + frameInfo.FilePath = metadataReader.GetString(metadataReader.GetDocument(bestPointSoFar.Value.Document).Name); + } + } + } + + private MetadataReader GetMetadataReader(string assemblyPath) + { + MetadataReaderProvider provider = null; + if (!_cache.TryGetValue(assemblyPath, out provider)) + { + var pdbPath = GetPdbPath(assemblyPath); + + if (!string.IsNullOrEmpty(pdbPath) && File.Exists(pdbPath) && IsPortable(pdbPath)) + { + var pdbStream = File.OpenRead(pdbPath); + provider = MetadataReaderProvider.FromPortablePdbStream(pdbStream); + } + + _cache[assemblyPath] = provider; + } + + return provider?.GetMetadataReader(); + } + + private static string GetPdbPath(string assemblyPath) + { + if (string.IsNullOrEmpty(assemblyPath)) + { + return null; + } + + if (File.Exists(assemblyPath)) + { + var peStream = File.OpenRead(assemblyPath); + + using (var peReader = new PEReader(peStream)) + { + foreach (var entry in peReader.ReadDebugDirectory()) + { + if (entry.Type == DebugDirectoryEntryType.CodeView) + { + var codeViewData = peReader.ReadCodeViewDebugDirectoryData(entry); + var peDirectory = Path.GetDirectoryName(assemblyPath); + return Path.Combine(peDirectory, Path.GetFileName(codeViewData.Path)); + } + } + } + } + + return null; + } + + private static bool IsPortable(string pdbPath) + { + using (var pdbStream = File.OpenRead(pdbPath)) + { + return pdbStream.ReadByte() == 'B' && + pdbStream.ReadByte() == 'S' && + pdbStream.ReadByte() == 'J' && + pdbStream.ReadByte() == 'B'; + } + } + + public void Dispose() + { + foreach (var entry in _cache) + { + entry.Value?.Dispose(); + } + + _cache.Clear(); + } + } +} diff --git a/src/Shared/StackTrace/StackFrame/StackFrameInfo.cs b/src/Shared/StackTrace/StackFrame/StackFrameInfo.cs new file mode 100644 index 0000000000..ffd91f213c --- /dev/null +++ b/src/Shared/StackTrace/StackFrame/StackFrameInfo.cs @@ -0,0 +1,18 @@ +// 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.Diagnostics; + +namespace Microsoft.Extensions.StackTrace.Sources +{ + internal class StackFrameInfo + { + public int LineNumber { get; set; } + + public string FilePath { get; set; } + + public StackFrame StackFrame { get; set; } + + public MethodDisplayInfo MethodDisplayInfo { get; set; } + } +} diff --git a/src/Shared/StackTrace/StackFrame/StackFrameSourceCodeInfo.cs b/src/Shared/StackTrace/StackFrame/StackFrameSourceCodeInfo.cs new file mode 100644 index 0000000000..2932e083b1 --- /dev/null +++ b/src/Shared/StackTrace/StackFrame/StackFrameSourceCodeInfo.cs @@ -0,0 +1,54 @@ +// 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.Collections.Generic; +using System.Linq; + +namespace Microsoft.Extensions.StackTrace.Sources +{ + /// + /// Contains the source code where the exception occurred. + /// + internal class StackFrameSourceCodeInfo + { + /// + /// Function containing instruction + /// + public string Function { get; set; } + + /// + /// File containing the instruction + /// + public string File { get; set; } + + /// + /// The line number of the instruction + /// + public int Line { get; set; } + + /// + /// The line preceding the frame line + /// + public int PreContextLine { get; set; } + + /// + /// Lines of code before the actual error line(s). + /// + public IEnumerable PreContextCode { get; set; } = Enumerable.Empty(); + + /// + /// Line(s) of code responsible for the error. + /// + public IEnumerable ContextCode { get; set; } = Enumerable.Empty(); + + /// + /// Lines of code after the actual error line(s). + /// + public IEnumerable PostContextCode { get; set; } = Enumerable.Empty(); + + /// + /// Specific error details for this stack frame. + /// + public string ErrorDetails { get; set; } + } +} diff --git a/src/Shared/StackTrace/StackFrame/StackTraceHelper.cs b/src/Shared/StackTrace/StackFrame/StackTraceHelper.cs new file mode 100644 index 0000000000..5ce9a40903 --- /dev/null +++ b/src/Shared/StackTrace/StackFrame/StackTraceHelper.cs @@ -0,0 +1,261 @@ +// 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.Collections; +using System.Collections.Generic; +using System.Diagnostics; +using System.Linq; +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.ExceptionServices; +using Microsoft.Extensions.Internal; + +namespace Microsoft.Extensions.StackTrace.Sources +{ + internal class StackTraceHelper + { + public static IList GetFrames(Exception exception) + { + var frames = new List(); + + if (exception == null) + { + return frames; + } + + using (var portablePdbReader = new PortablePdbReader()) + { + var needFileInfo = true; + var stackTrace = new System.Diagnostics.StackTrace(exception, needFileInfo); + var stackFrames = stackTrace.GetFrames(); + + if (stackFrames == null) + { + return frames; + } + + for (var i = 0; i < stackFrames.Length; i++) + { + var frame = stackFrames[i]; + var method = frame.GetMethod(); + + // Always show last stackFrame + if (!ShowInStackTrace(method) && i < stackFrames.Length - 1) + { + continue; + } + + var stackFrame = new StackFrameInfo + { + StackFrame = frame, + FilePath = frame.GetFileName(), + LineNumber = frame.GetFileLineNumber(), + MethodDisplayInfo = GetMethodDisplayString(frame.GetMethod()), + }; + + if (string.IsNullOrEmpty(stackFrame.FilePath)) + { + // .NET Framework and older versions of mono don't support portable PDBs + // so we read it manually to get file name and line information + portablePdbReader.PopulateStackFrame(stackFrame, method, frame.GetILOffset()); + } + + frames.Add(stackFrame); + } + + return frames; + } + } + + internal static MethodDisplayInfo GetMethodDisplayString(MethodBase method) + { + // Special case: no method available + if (method == null) + { + return null; + } + + var methodDisplayInfo = new MethodDisplayInfo(); + + // Type name + var type = method.DeclaringType; + + var methodName = method.Name; + + if (type != null && type.IsDefined(typeof(CompilerGeneratedAttribute)) && + (typeof(IAsyncStateMachine).IsAssignableFrom(type) || typeof(IEnumerator).IsAssignableFrom(type))) + { + // Convert StateMachine methods to correct overload +MoveNext() + if (TryResolveStateMachineMethod(ref method, out type)) + { + methodDisplayInfo.SubMethod = methodName; + } + } + // ResolveStateMachineMethod may have set declaringType to null + if (type != null) + { + methodDisplayInfo.DeclaringTypeName = TypeNameHelper.GetTypeDisplayName(type, includeGenericParameterNames: true); + } + + // Method name + methodDisplayInfo.Name = method.Name; + if (method.IsGenericMethod) + { + var genericArguments = string.Join(", ", method.GetGenericArguments() + .Select(arg => TypeNameHelper.GetTypeDisplayName(arg, fullName: false, includeGenericParameterNames: true))); + methodDisplayInfo.GenericArguments += "<" + genericArguments + ">"; + } + + // Method parameters + methodDisplayInfo.Parameters = method.GetParameters().Select(parameter => + { + var parameterType = parameter.ParameterType; + + var prefix = string.Empty; + if (parameter.IsOut) + { + prefix = "out"; + } + else if (parameterType != null && parameterType.IsByRef) + { + prefix = "ref"; + } + + var parameterTypeString = "?"; + if (parameterType != null) + { + if (parameterType.IsByRef) + { + parameterType = parameterType.GetElementType(); + } + + parameterTypeString = TypeNameHelper.GetTypeDisplayName(parameterType, fullName: false, includeGenericParameterNames: true); + } + + return new ParameterDisplayInfo + { + Prefix = prefix, + Name = parameter.Name, + Type = parameterTypeString, + }; + }); + + return methodDisplayInfo; + } + + private static bool ShowInStackTrace(MethodBase method) + { + Debug.Assert(method != null); + + // Don't show any methods marked with the StackTraceHiddenAttribute + // https://github.com/dotnet/coreclr/pull/14652 + if (HasStackTraceHiddenAttribute(method)) + { + return false; + } + + + var type = method.DeclaringType; + if (type == null) + { + return true; + } + + if (HasStackTraceHiddenAttribute(type)) + { + return false; + } + + // Fallbacks for runtime pre-StackTraceHiddenAttribute + if (type == typeof(ExceptionDispatchInfo) && method.Name == "Throw") + { + return false; + } + else if (type == typeof(TaskAwaiter) || + type == typeof(TaskAwaiter<>) || + type == typeof(ConfiguredTaskAwaitable.ConfiguredTaskAwaiter) || + type == typeof(ConfiguredTaskAwaitable<>.ConfiguredTaskAwaiter)) + { + switch (method.Name) + { + case "HandleNonSuccessAndDebuggerNotification": + case "ThrowForNonSuccess": + case "ValidateEnd": + case "GetResult": + return false; + } + } + + return true; + } + + private static bool TryResolveStateMachineMethod(ref MethodBase method, out Type declaringType) + { + Debug.Assert(method != null); + Debug.Assert(method.DeclaringType != null); + + declaringType = method.DeclaringType; + + var parentType = declaringType.DeclaringType; + if (parentType == null) + { + return false; + } + + var methods = parentType.GetMethods(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Static | BindingFlags.Instance | BindingFlags.DeclaredOnly); + if (methods == null) + { + return false; + } + + foreach (var candidateMethod in methods) + { + var attributes = candidateMethod.GetCustomAttributes(); + if (attributes == null) + { + continue; + } + + foreach (var asma in attributes) + { + if (asma.StateMachineType == declaringType) + { + method = candidateMethod; + declaringType = candidateMethod.DeclaringType; + // Mark the iterator as changed; so it gets the + annotation of the original method + // async statemachines resolve directly to their builder methods so aren't marked as changed + return asma is IteratorStateMachineAttribute; + } + } + } + + return false; + } + + private static bool HasStackTraceHiddenAttribute(MemberInfo memberInfo) + { + IList attributes; + try + { + // Accessing MembmerInfo.GetCustomAttributesData throws for some types (such as types in dynamically generated assemblies). + // We'll skip looking up StackTraceHiddenAttributes on such types. + attributes = memberInfo.GetCustomAttributesData(); + } + catch + { + return false; + } + + for (var i = 0; i < attributes.Count; i++) + { + if (attributes[i].AttributeType.Name == "StackTraceHiddenAttribute") + { + return true; + } + } + + return false; + } + } +} diff --git a/src/Shared/TypeNameHelper/TypeNameHelper.cs b/src/Shared/TypeNameHelper/TypeNameHelper.cs new file mode 100644 index 0000000000..1cc7468646 --- /dev/null +++ b/src/Shared/TypeNameHelper/TypeNameHelper.cs @@ -0,0 +1,160 @@ +// 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.Text; +using System.Collections.Generic; + +namespace Microsoft.Extensions.Internal +{ + internal class TypeNameHelper + { + private static readonly Dictionary _builtInTypeNames = new Dictionary + { + { typeof(void), "void" }, + { typeof(bool), "bool" }, + { typeof(byte), "byte" }, + { typeof(char), "char" }, + { typeof(decimal), "decimal" }, + { typeof(double), "double" }, + { typeof(float), "float" }, + { typeof(int), "int" }, + { typeof(long), "long" }, + { typeof(object), "object" }, + { typeof(sbyte), "sbyte" }, + { typeof(short), "short" }, + { typeof(string), "string" }, + { typeof(uint), "uint" }, + { typeof(ulong), "ulong" }, + { typeof(ushort), "ushort" } + }; + + public static string GetTypeDisplayName(object item, bool fullName = true) + { + return item == null ? null : GetTypeDisplayName(item.GetType(), fullName); + } + + /// + /// Pretty print a type name. + /// + /// The . + /// true to print a fully qualified name. + /// true to include generic parameter names. + /// The pretty printed type name. + public static string GetTypeDisplayName(Type type, bool fullName = true, bool includeGenericParameterNames = false) + { + var builder = new StringBuilder(); + ProcessType(builder, type, new DisplayNameOptions(fullName, includeGenericParameterNames)); + return builder.ToString(); + } + + private static void ProcessType(StringBuilder builder, Type type, DisplayNameOptions options) + { + if (type.IsGenericType) + { + var genericArguments = type.GetGenericArguments(); + ProcessGenericType(builder, type, genericArguments, genericArguments.Length, options); + } + else if (type.IsArray) + { + ProcessArrayType(builder, type, options); + } + else if (_builtInTypeNames.TryGetValue(type, out var builtInName)) + { + builder.Append(builtInName); + } + else if (type.IsGenericParameter) + { + if (options.IncludeGenericParameterNames) + { + builder.Append(type.Name); + } + } + else + { + builder.Append(options.FullName ? type.FullName : type.Name); + } + } + + private static void ProcessArrayType(StringBuilder builder, Type type, DisplayNameOptions options) + { + var innerType = type; + while (innerType.IsArray) + { + innerType = innerType.GetElementType(); + } + + ProcessType(builder, innerType, options); + + while (type.IsArray) + { + builder.Append('['); + builder.Append(',', type.GetArrayRank() - 1); + builder.Append(']'); + type = type.GetElementType(); + } + } + + private static void ProcessGenericType(StringBuilder builder, Type type, Type[] genericArguments, int length, DisplayNameOptions options) + { + var offset = 0; + if (type.IsNested) + { + offset = type.DeclaringType.GetGenericArguments().Length; + } + + if (options.FullName) + { + if (type.IsNested) + { + ProcessGenericType(builder, type.DeclaringType, genericArguments, offset, options); + builder.Append('+'); + } + else if (!string.IsNullOrEmpty(type.Namespace)) + { + builder.Append(type.Namespace); + builder.Append('.'); + } + } + + var genericPartIndex = type.Name.IndexOf('`'); + if (genericPartIndex <= 0) + { + builder.Append(type.Name); + return; + } + + builder.Append(type.Name, 0, genericPartIndex); + + builder.Append('<'); + for (var i = offset; i < length; i++) + { + ProcessType(builder, genericArguments[i], options); + if (i + 1 == length) + { + continue; + } + + builder.Append(','); + if (options.IncludeGenericParameterNames || !genericArguments[i + 1].IsGenericParameter) + { + builder.Append(' '); + } + } + builder.Append('>'); + } + + private struct DisplayNameOptions + { + public DisplayNameOptions(bool fullName, bool includeGenericParameterNames) + { + FullName = fullName; + IncludeGenericParameterNames = includeGenericParameterNames; + } + + public bool FullName { get; } + + public bool IncludeGenericParameterNames { get; } + } + } +} diff --git a/src/Shared/ValueStopwatch/ValueStopwatch.cs b/src/Shared/ValueStopwatch/ValueStopwatch.cs new file mode 100644 index 0000000000..f99a084aeb --- /dev/null +++ b/src/Shared/ValueStopwatch/ValueStopwatch.cs @@ -0,0 +1,39 @@ +// 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.Diagnostics; + +namespace Microsoft.Extensions.Internal +{ + internal struct ValueStopwatch + { + private static readonly double TimestampToTicks = TimeSpan.TicksPerSecond / (double)Stopwatch.Frequency; + + private long _startTimestamp; + + public bool IsActive => _startTimestamp != 0; + + private ValueStopwatch(long startTimestamp) + { + _startTimestamp = startTimestamp; + } + + public static ValueStopwatch StartNew() => new ValueStopwatch(Stopwatch.GetTimestamp()); + + public TimeSpan GetElapsedTime() + { + // Start timestamp can't be zero in an initialized ValueStopwatch. It would have to be literally the first thing executed when the machine boots to be 0. + // So it being 0 is a clear indication of default(ValueStopwatch) + if (!IsActive) + { + throw new InvalidOperationException("An uninitialized, or 'default', ValueStopwatch cannot be used to get elapsed time."); + } + + var end = Stopwatch.GetTimestamp(); + var timestampDelta = end - _startTimestamp; + var ticks = (long)(TimestampToTicks * timestampDelta); + return new TimeSpan(ticks); + } + } +} diff --git a/src/Shared/WebEncoders/Properties/EncoderResources.cs b/src/Shared/WebEncoders/Properties/EncoderResources.cs new file mode 100644 index 0000000000..3474ae82c5 --- /dev/null +++ b/src/Shared/WebEncoders/Properties/EncoderResources.cs @@ -0,0 +1,38 @@ +// 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.Globalization; + +namespace Microsoft.Extensions.WebEncoders.Sources +{ + // TODO using a resx file. project.json, unfortunately, fails to embed resx files when there are also compile items + // in the contentFiles section. Revisit once we convert repos to MSBuild + internal static class EncoderResources + { + /// + /// Invalid {0}, {1} or {2} length. + /// + internal static readonly string WebEncoders_InvalidCountOffsetOrLength = "Invalid {0}, {1} or {2} length."; + + /// + /// Malformed input: {0} is an invalid input length. + /// + internal static readonly string WebEncoders_MalformedInput = "Malformed input: {0} is an invalid input length."; + + /// + /// Invalid {0}, {1} or {2} length. + /// + internal static string FormatWebEncoders_InvalidCountOffsetOrLength(object p0, object p1, object p2) + { + return string.Format(CultureInfo.CurrentCulture, WebEncoders_InvalidCountOffsetOrLength, p0, p1, p2); + } + + /// + /// Malformed input: {0} is an invalid input length. + /// + internal static string FormatWebEncoders_MalformedInput(object p0) + { + return string.Format(CultureInfo.CurrentCulture, WebEncoders_MalformedInput, p0); + } + } +} diff --git a/src/Shared/WebEncoders/WebEncoders.cs b/src/Shared/WebEncoders/WebEncoders.cs new file mode 100644 index 0000000000..17068ae67a --- /dev/null +++ b/src/Shared/WebEncoders/WebEncoders.cs @@ -0,0 +1,388 @@ +// 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.Diagnostics; +using System.Globalization; +using Microsoft.Extensions.WebEncoders.Sources; + +#if WebEncoders_In_WebUtilities +namespace Microsoft.AspNetCore.WebUtilities +#else +namespace Microsoft.Extensions.Internal +#endif +{ + /// + /// Contains utility APIs to assist with common encoding and decoding operations. + /// +#if WebEncoders_In_WebUtilities + public +#else + internal +#endif + static class WebEncoders + { + private static readonly byte[] EmptyBytes = new byte[0]; + + /// + /// Decodes a base64url-encoded string. + /// + /// The base64url-encoded input to decode. + /// The base64url-decoded form of the input. + /// + /// The input must not contain any whitespace or padding characters. + /// Throws if the input is malformed. + /// + public static byte[] Base64UrlDecode(string input) + { + if (input == null) + { + throw new ArgumentNullException(nameof(input)); + } + + return Base64UrlDecode(input, offset: 0, count: input.Length); + } + + /// + /// Decodes a base64url-encoded substring of a given string. + /// + /// A string containing the base64url-encoded input to decode. + /// The position in at which decoding should begin. + /// The number of characters in to decode. + /// The base64url-decoded form of the input. + /// + /// The input must not contain any whitespace or padding characters. + /// Throws if the input is malformed. + /// + public static byte[] Base64UrlDecode(string input, int offset, int count) + { + if (input == null) + { + throw new ArgumentNullException(nameof(input)); + } + + ValidateParameters(input.Length, nameof(input), offset, count); + + // Special-case empty input + if (count == 0) + { + return EmptyBytes; + } + + // Create array large enough for the Base64 characters, not just shorter Base64-URL-encoded form. + var buffer = new char[GetArraySizeRequiredToDecode(count)]; + + return Base64UrlDecode(input, offset, buffer, bufferOffset: 0, count: count); + } + + /// + /// Decodes a base64url-encoded into a byte[]. + /// + /// A string containing the base64url-encoded input to decode. + /// The position in at which decoding should begin. + /// + /// Scratch buffer to hold the s to decode. Array must be large enough to hold + /// and characters as well as Base64 padding + /// characters. Content is not preserved. + /// + /// + /// The offset into at which to begin writing the s to decode. + /// + /// The number of characters in to decode. + /// The base64url-decoded form of the . + /// + /// The input must not contain any whitespace or padding characters. + /// Throws if the input is malformed. + /// + public static byte[] Base64UrlDecode(string input, int offset, char[] buffer, int bufferOffset, int count) + { + if (input == null) + { + throw new ArgumentNullException(nameof(input)); + } + if (buffer == null) + { + throw new ArgumentNullException(nameof(buffer)); + } + + ValidateParameters(input.Length, nameof(input), offset, count); + if (bufferOffset < 0) + { + throw new ArgumentOutOfRangeException(nameof(bufferOffset)); + } + + if (count == 0) + { + return EmptyBytes; + } + + // Assumption: input is base64url encoded without padding and contains no whitespace. + + var paddingCharsToAdd = GetNumBase64PaddingCharsToAddForDecode(count); + var arraySizeRequired = checked(count + paddingCharsToAdd); + Debug.Assert(arraySizeRequired % 4 == 0, "Invariant: Array length must be a multiple of 4."); + + if (buffer.Length - bufferOffset < arraySizeRequired) + { + throw new ArgumentException( + string.Format( + CultureInfo.CurrentCulture, + EncoderResources.WebEncoders_InvalidCountOffsetOrLength, + nameof(count), + nameof(bufferOffset), + nameof(input)), + nameof(count)); + } + + // Copy input into buffer, fixing up '-' -> '+' and '_' -> '/'. + var i = bufferOffset; + for (var j = offset; i - bufferOffset < count; i++, j++) + { + var ch = input[j]; + if (ch == '-') + { + buffer[i] = '+'; + } + else if (ch == '_') + { + buffer[i] = '/'; + } + else + { + buffer[i] = ch; + } + } + + // Add the padding characters back. + for (; paddingCharsToAdd > 0; i++, paddingCharsToAdd--) + { + buffer[i] = '='; + } + + // Decode. + // If the caller provided invalid base64 chars, they'll be caught here. + return Convert.FromBase64CharArray(buffer, bufferOffset, arraySizeRequired); + } + + /// + /// Gets the minimum char[] size required for decoding of characters + /// with the method. + /// + /// The number of characters to decode. + /// + /// The minimum char[] size required for decoding of characters. + /// + public static int GetArraySizeRequiredToDecode(int count) + { + if (count < 0) + { + throw new ArgumentOutOfRangeException(nameof(count)); + } + + if (count == 0) + { + return 0; + } + + var numPaddingCharsToAdd = GetNumBase64PaddingCharsToAddForDecode(count); + + return checked(count + numPaddingCharsToAdd); + } + + /// + /// Encodes using base64url encoding. + /// + /// The binary input to encode. + /// The base64url-encoded form of . + public static string Base64UrlEncode(byte[] input) + { + if (input == null) + { + throw new ArgumentNullException(nameof(input)); + } + + return Base64UrlEncode(input, offset: 0, count: input.Length); + } + + /// + /// Encodes using base64url encoding. + /// + /// The binary input to encode. + /// The offset into at which to begin encoding. + /// The number of bytes from to encode. + /// The base64url-encoded form of . + public static string Base64UrlEncode(byte[] input, int offset, int count) + { + if (input == null) + { + throw new ArgumentNullException(nameof(input)); + } + + ValidateParameters(input.Length, nameof(input), offset, count); + + // Special-case empty input + if (count == 0) + { + return string.Empty; + } + + var buffer = new char[GetArraySizeRequiredToEncode(count)]; + var numBase64Chars = Base64UrlEncode(input, offset, buffer, outputOffset: 0, count: count); + + return new String(buffer, startIndex: 0, length: numBase64Chars); + } + + /// + /// Encodes using base64url encoding. + /// + /// The binary input to encode. + /// The offset into at which to begin encoding. + /// + /// Buffer to receive the base64url-encoded form of . Array must be large enough to + /// hold characters and the full base64-encoded form of + /// , including padding characters. + /// + /// + /// The offset into at which to begin writing the base64url-encoded form of + /// . + /// + /// The number of bytes from to encode. + /// + /// The number of characters written to , less any padding characters. + /// + public static int Base64UrlEncode(byte[] input, int offset, char[] output, int outputOffset, int count) + { + if (input == null) + { + throw new ArgumentNullException(nameof(input)); + } + if (output == null) + { + throw new ArgumentNullException(nameof(output)); + } + + ValidateParameters(input.Length, nameof(input), offset, count); + if (outputOffset < 0) + { + throw new ArgumentOutOfRangeException(nameof(outputOffset)); + } + + var arraySizeRequired = GetArraySizeRequiredToEncode(count); + if (output.Length - outputOffset < arraySizeRequired) + { + throw new ArgumentException( + string.Format( + CultureInfo.CurrentCulture, + EncoderResources.WebEncoders_InvalidCountOffsetOrLength, + nameof(count), + nameof(outputOffset), + nameof(output)), + nameof(count)); + } + + // Special-case empty input. + if (count == 0) + { + return 0; + } + + // Use base64url encoding with no padding characters. See RFC 4648, Sec. 5. + + // Start with default Base64 encoding. + var numBase64Chars = Convert.ToBase64CharArray(input, offset, count, output, outputOffset); + + // Fix up '+' -> '-' and '/' -> '_'. Drop padding characters. + for (var i = outputOffset; i - outputOffset < numBase64Chars; i++) + { + var ch = output[i]; + if (ch == '+') + { + output[i] = '-'; + } + else if (ch == '/') + { + output[i] = '_'; + } + else if (ch == '=') + { + // We've reached a padding character; truncate the remainder. + return i - outputOffset; + } + } + + return numBase64Chars; + } + + /// + /// Get the minimum output char[] size required for encoding + /// s with the method. + /// + /// The number of characters to encode. + /// + /// The minimum output char[] size required for encoding s. + /// + public static int GetArraySizeRequiredToEncode(int count) + { + var numWholeOrPartialInputBlocks = checked(count + 2) / 3; + return checked(numWholeOrPartialInputBlocks * 4); + } + + private static int GetNumBase64PaddingCharsInString(string str) + { + // Assumption: input contains a well-formed base64 string with no whitespace. + + // base64 guaranteed have 0 - 2 padding characters. + if (str[str.Length - 1] == '=') + { + if (str[str.Length - 2] == '=') + { + return 2; + } + return 1; + } + return 0; + } + + private static int GetNumBase64PaddingCharsToAddForDecode(int inputLength) + { + switch (inputLength % 4) + { + case 0: + return 0; + case 2: + return 2; + case 3: + return 1; + default: + throw new FormatException( + string.Format( + CultureInfo.CurrentCulture, + EncoderResources.WebEncoders_MalformedInput, + inputLength)); + } + } + + private static void ValidateParameters(int bufferLength, string inputName, int offset, int count) + { + if (offset < 0) + { + throw new ArgumentOutOfRangeException(nameof(offset)); + } + if (count < 0) + { + throw new ArgumentOutOfRangeException(nameof(count)); + } + if (bufferLength - offset < count) + { + throw new ArgumentException( + string.Format( + CultureInfo.CurrentCulture, + EncoderResources.WebEncoders_InvalidCountOffsetOrLength, + nameof(count), + nameof(offset), + inputName), + nameof(count)); + } + } + } +} diff --git a/src/Shared/test/Shared.Tests/ArgumentEscaperTests.cs b/src/Shared/test/Shared.Tests/ArgumentEscaperTests.cs new file mode 100644 index 0000000000..a706b05bfc --- /dev/null +++ b/src/Shared/test/Shared.Tests/ArgumentEscaperTests.cs @@ -0,0 +1,24 @@ +// 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 Xunit; + +namespace Microsoft.Extensions.CommandLineUtils +{ + public class ArgumentEscaperTests + { + [Theory] + [InlineData(new[] { "one", "two", "three" }, "one two three")] + [InlineData(new[] { "line1\nline2", "word1\tword2" }, "\"line1\nline2\" \"word1\tword2\"")] + [InlineData(new[] { "with spaces" }, "\"with spaces\"")] + [InlineData(new[] { @"with\backslash" }, @"with\backslash")] + [InlineData(new[] { @"""quotedwith\backslash""" }, @"\""quotedwith\backslash\""")] + [InlineData(new[] { @"C:\Users\" }, @"C:\Users\")] + [InlineData(new[] { @"C:\Program Files\dotnet\" }, @"""C:\Program Files\dotnet\\""")] + [InlineData(new[] { @"backslash\""preceedingquote" }, @"backslash\\\""preceedingquote")] + public void EscapesArguments(string[] args, string expected) + { + Assert.Equal(expected, ArgumentEscaper.EscapeAndConcatenate(args)); + } + } +} diff --git a/src/Shared/test/Shared.Tests/CertificateManagerTests.cs b/src/Shared/test/Shared.Tests/CertificateManagerTests.cs new file mode 100644 index 0000000000..613f5c966f --- /dev/null +++ b/src/Shared/test/Shared.Tests/CertificateManagerTests.cs @@ -0,0 +1,304 @@ +// 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. + +#if NETCOREAPP2_0 || NETCOREAPP2_1 + +using System; +using System.IO; +using System.Linq; +using System.Runtime.InteropServices; +using System.Security.Cryptography; +using System.Security.Cryptography.X509Certificates; +using System.Text; +using Xunit; +using Xunit.Abstractions; + +namespace Microsoft.AspNetCore.Certificates.Generation.Tests +{ + public class CertificateManagerTests + { + public CertificateManagerTests(ITestOutputHelper output) + { + Output = output; + } + + public const string TestCertificateSubject = "CN=aspnet.test"; + + public ITestOutputHelper Output { get; } + + [Fact] + public void EnsureCreateHttpsCertificate_CreatesACertificate_WhenThereAreNoHttpsCertificates() + { + try + { + // Arrange + const string CertificateName = nameof(EnsureCreateHttpsCertificate_CreatesACertificate_WhenThereAreNoHttpsCertificates) + ".cer"; + var manager = new CertificateManager(); + + manager.RemoveAllCertificates(CertificatePurpose.HTTPS, StoreName.My, StoreLocation.CurrentUser, TestCertificateSubject); + if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) + { + manager.RemoveAllCertificates(CertificatePurpose.HTTPS, StoreName.Root, StoreLocation.CurrentUser, TestCertificateSubject); + } + + // Act + DateTimeOffset now = DateTimeOffset.UtcNow; + now = new DateTimeOffset(now.Year, now.Month, now.Day, now.Hour, now.Minute, now.Second, 0, now.Offset); + var result = manager.EnsureAspNetCoreHttpsDevelopmentCertificate(now, now.AddYears(1), CertificateName, trust: false, subject: TestCertificateSubject); + + // Assert + Assert.Equal(EnsureCertificateResult.Succeeded, result); + Assert.True(File.Exists(CertificateName)); + + var exportedCertificate = new X509Certificate2(File.ReadAllBytes(CertificateName)); + Assert.NotNull(exportedCertificate); + Assert.False(exportedCertificate.HasPrivateKey); + + var httpsCertificates = manager.ListCertificates(CertificatePurpose.HTTPS, StoreName.My, StoreLocation.CurrentUser, isValid: false); + var httpsCertificate = Assert.Single(httpsCertificates, c => c.Subject == TestCertificateSubject); + Assert.True(httpsCertificate.HasPrivateKey); + Assert.Equal(TestCertificateSubject, httpsCertificate.Subject); + Assert.Equal(TestCertificateSubject, httpsCertificate.Issuer); + Assert.Equal("sha256RSA", httpsCertificate.SignatureAlgorithm.FriendlyName); + Assert.Equal("1.2.840.113549.1.1.11", httpsCertificate.SignatureAlgorithm.Value); + + Assert.Equal(now.LocalDateTime, httpsCertificate.NotBefore); + Assert.Equal(now.AddYears(1).LocalDateTime, httpsCertificate.NotAfter); + Assert.Contains( + httpsCertificate.Extensions.OfType(), + e => e is X509BasicConstraintsExtension basicConstraints && + basicConstraints.Critical == true && + basicConstraints.CertificateAuthority == false && + basicConstraints.HasPathLengthConstraint == false && + basicConstraints.PathLengthConstraint == 0); + + Assert.Contains( + httpsCertificate.Extensions.OfType(), + e => e is X509KeyUsageExtension keyUsage && + keyUsage.Critical == true && + keyUsage.KeyUsages == X509KeyUsageFlags.KeyEncipherment); + + Assert.Contains( + httpsCertificate.Extensions.OfType(), + e => e is X509EnhancedKeyUsageExtension enhancedKeyUsage && + enhancedKeyUsage.Critical == true && + enhancedKeyUsage.EnhancedKeyUsages.OfType().Single() is Oid keyUsage && + keyUsage.Value == "1.3.6.1.5.5.7.3.1"); + + // Subject alternative name + Assert.Contains( + httpsCertificate.Extensions.OfType(), + e => e.Critical == true && + e.Oid.Value == "2.5.29.17"); + + // ASP.NET HTTPS Development certificate extension + Assert.Contains( + httpsCertificate.Extensions.OfType(), + e => e.Critical == false && + e.Oid.Value == "1.3.6.1.4.1.311.84.1.1" && + Encoding.ASCII.GetString(e.RawData) == "ASP.NET Core HTTPS development certificate"); + + Assert.Equal(httpsCertificate.GetCertHashString(), exportedCertificate.GetCertHashString()); + + } + catch (Exception e) + { + Output.WriteLine(e.Message); + ListCertificates(Output); + throw; + } + } + + private void ListCertificates(ITestOutputHelper output) + { + using (var store = new X509Store(StoreName.My, StoreLocation.CurrentUser)) + { + store.Open(OpenFlags.ReadOnly); + var certificates = store.Certificates; + foreach (var certificate in certificates) + { + Output.WriteLine($"Certificate: '{Convert.ToBase64String(certificate.Export(X509ContentType.Cert))}'."); + certificate.Dispose(); + } + + store.Close(); + } + } + + [Fact] + public void EnsureCreateHttpsCertificate_DoesNotCreateACertificate_WhenThereIsAnExistingHttpsCertificates() + { + // Arrange + const string CertificateName = nameof(EnsureCreateHttpsCertificate_DoesNotCreateACertificate_WhenThereIsAnExistingHttpsCertificates) + ".pfx"; + var certificatePassword = Guid.NewGuid().ToString(); + + var manager = new CertificateManager(); + + manager.RemoveAllCertificates(CertificatePurpose.HTTPS, StoreName.My, StoreLocation.CurrentUser, TestCertificateSubject); + if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) + { + manager.RemoveAllCertificates(CertificatePurpose.HTTPS, StoreName.Root, StoreLocation.CurrentUser, TestCertificateSubject); + } + + DateTimeOffset now = DateTimeOffset.UtcNow; + now = new DateTimeOffset(now.Year, now.Month, now.Day, now.Hour, now.Minute, now.Second, 0, now.Offset); + manager.EnsureAspNetCoreHttpsDevelopmentCertificate(now, now.AddYears(1), path: null, trust: false, subject: TestCertificateSubject); + + var httpsCertificate = manager.ListCertificates(CertificatePurpose.HTTPS, StoreName.My, StoreLocation.CurrentUser, isValid: false).Single(c => c.Subject == TestCertificateSubject); + + // Act + var result = manager.EnsureAspNetCoreHttpsDevelopmentCertificate(now, now.AddYears(1), CertificateName, trust: false, includePrivateKey: true, password: certificatePassword, subject: TestCertificateSubject); + + // Assert + Assert.Equal(EnsureCertificateResult.ValidCertificatePresent, result); + Assert.True(File.Exists(CertificateName)); + + var exportedCertificate = new X509Certificate2(File.ReadAllBytes(CertificateName), certificatePassword); + Assert.NotNull(exportedCertificate); + Assert.True(exportedCertificate.HasPrivateKey); + + + Assert.Equal(httpsCertificate.GetCertHashString(), exportedCertificate.GetCertHashString()); + } + + [Fact(Skip = "Requires user interaction")] + public void EnsureAspNetCoreHttpsDevelopmentCertificate_ReturnsCorrectResult_WhenUserCancelsTrustStepOnWindows() + { + var manager = new CertificateManager(); + + manager.RemoveAllCertificates(CertificatePurpose.HTTPS, StoreName.My, StoreLocation.CurrentUser, TestCertificateSubject); + if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) + { + manager.RemoveAllCertificates(CertificatePurpose.HTTPS, StoreName.Root, StoreLocation.CurrentUser, TestCertificateSubject); + } + + DateTimeOffset now = DateTimeOffset.UtcNow; + now = new DateTimeOffset(now.Year, now.Month, now.Day, now.Hour, now.Minute, now.Second, 0, now.Offset); + var trustFailed = manager.EnsureAspNetCoreHttpsDevelopmentCertificate(now, now.AddYears(1), path: null, trust: true, subject: TestCertificateSubject); + + Assert.Equal(EnsureCertificateResult.UserCancelledTrustStep, trustFailed); + } + + [Fact(Skip = "Requires user interaction")] + public void EnsureAspNetCoreHttpsDevelopmentCertificate_CanRemoveCertificates() + { + var manager = new CertificateManager(); + + DateTimeOffset now = DateTimeOffset.UtcNow; + now = new DateTimeOffset(now.Year, now.Month, now.Day, now.Hour, now.Minute, now.Second, 0, now.Offset); + manager.EnsureAspNetCoreHttpsDevelopmentCertificate(now, now.AddYears(1), path: null, trust: true, subject: TestCertificateSubject); + + manager.CleanupHttpsCertificates(TestCertificateSubject); + + Assert.Empty(manager.ListCertificates(CertificatePurpose.HTTPS, StoreName.My, StoreLocation.CurrentUser, isValid: false).Where(c => c.Subject == TestCertificateSubject)); + if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) + { + Assert.Empty(manager.ListCertificates(CertificatePurpose.HTTPS, StoreName.Root, StoreLocation.CurrentUser, isValid: false).Where(c => c.Subject == TestCertificateSubject)); + } + } + + [Fact] + public void EnsureCreateIdentityTokenSigningCertificate_CreatesACertificate_WhenThereAreNoHttpsCertificates() + { + // Arrange + const string CertificateName = nameof(EnsureCreateIdentityTokenSigningCertificate_CreatesACertificate_WhenThereAreNoHttpsCertificates) + ".cer"; + var manager = new CertificateManager(); + + manager.RemoveAllCertificates(CertificatePurpose.Signing, StoreName.My, StoreLocation.CurrentUser, TestCertificateSubject); + if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) + { + manager.RemoveAllCertificates(CertificatePurpose.Signing, StoreName.Root, StoreLocation.CurrentUser, TestCertificateSubject); + } + + // Act + DateTimeOffset now = DateTimeOffset.UtcNow; + now = new DateTimeOffset(now.Year, now.Month, now.Day, now.Hour, now.Minute, now.Second, 0, now.Offset); + var result = manager.EnsureAspNetCoreApplicationTokensDevelopmentCertificate(now, now.AddYears(1), CertificateName, trust: false, subject: TestCertificateSubject); + + // Assert + Assert.Equal(EnsureCertificateResult.Succeeded, result); + Assert.True(File.Exists(CertificateName)); + + var exportedCertificate = new X509Certificate2(File.ReadAllBytes(CertificateName)); + Assert.NotNull(exportedCertificate); + Assert.False(exportedCertificate.HasPrivateKey); + + var identityCertificates = manager.ListCertificates(CertificatePurpose.Signing, StoreName.My, StoreLocation.CurrentUser, isValid: false); + var identityCertificate = Assert.Single(identityCertificates, i => i.Subject == TestCertificateSubject); + Assert.True(identityCertificate.HasPrivateKey); + Assert.Equal(TestCertificateSubject, identityCertificate.Subject); + Assert.Equal(TestCertificateSubject, identityCertificate.Issuer); + Assert.Equal("sha256RSA", identityCertificate.SignatureAlgorithm.FriendlyName); + Assert.Equal("1.2.840.113549.1.1.11", identityCertificate.SignatureAlgorithm.Value); + + Assert.Equal(now.LocalDateTime, identityCertificate.NotBefore); + Assert.Equal(now.AddYears(1).LocalDateTime, identityCertificate.NotAfter); + Assert.Contains( + identityCertificate.Extensions.OfType(), + e => e is X509BasicConstraintsExtension basicConstraints && + basicConstraints.Critical == true && + basicConstraints.CertificateAuthority == false && + basicConstraints.HasPathLengthConstraint == false && + basicConstraints.PathLengthConstraint == 0); + + Assert.Contains( + identityCertificate.Extensions.OfType(), + e => e is X509KeyUsageExtension keyUsage && + keyUsage.Critical == true && + keyUsage.KeyUsages == X509KeyUsageFlags.DigitalSignature); + + Assert.Contains( + identityCertificate.Extensions.OfType(), + e => e is X509EnhancedKeyUsageExtension enhancedKeyUsage && + enhancedKeyUsage.Critical == true && + enhancedKeyUsage.EnhancedKeyUsages.OfType().Single() is Oid keyUsage && + keyUsage.Value == "1.3.6.1.5.5.7.3.1"); + + // ASP.NET Core Identity Json Web Token signing development certificate + Assert.Contains( + identityCertificate.Extensions.OfType(), + e => e.Critical == false && + e.Oid.Value == "1.3.6.1.4.1.311.84.1.2" && + Encoding.ASCII.GetString(e.RawData) == "ASP.NET Core Identity Json Web Token signing development certificate"); + + Assert.Equal(identityCertificate.GetCertHashString(), exportedCertificate.GetCertHashString()); + } + + [Fact] + public void EnsureCreateIdentityTokenSigningCertificate_DoesNotCreateACertificate_WhenThereIsAnExistingHttpsCertificates() + { + // Arrange + const string CertificateName = nameof(EnsureCreateIdentityTokenSigningCertificate_DoesNotCreateACertificate_WhenThereIsAnExistingHttpsCertificates) + ".pfx"; + var certificatePassword = Guid.NewGuid().ToString(); + + var manager = new CertificateManager(); + + manager.RemoveAllCertificates(CertificatePurpose.Signing, StoreName.My, StoreLocation.CurrentUser, TestCertificateSubject); + if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) + { + manager.RemoveAllCertificates(CertificatePurpose.Signing, StoreName.Root, StoreLocation.CurrentUser, TestCertificateSubject); + } + + DateTimeOffset now = DateTimeOffset.UtcNow; + now = new DateTimeOffset(now.Year, now.Month, now.Day, now.Hour, now.Minute, now.Second, 0, now.Offset); + manager.EnsureAspNetCoreApplicationTokensDevelopmentCertificate(now, now.AddYears(1), path: null, trust: false, subject: TestCertificateSubject); + + var identityTokenSigningCertificates = manager.ListCertificates(CertificatePurpose.Signing, StoreName.My, StoreLocation.CurrentUser, isValid: false).Single(c => c.Subject == TestCertificateSubject); + + // Act + var result = manager.EnsureAspNetCoreApplicationTokensDevelopmentCertificate(now, now.AddYears(1), CertificateName, trust: false, includePrivateKey: true, password: certificatePassword, subject: TestCertificateSubject); + + // Assert + Assert.Equal(EnsureCertificateResult.ValidCertificatePresent, result); + Assert.True(File.Exists(CertificateName)); + + var exportedCertificate = new X509Certificate2(File.ReadAllBytes(CertificateName), certificatePassword); + Assert.NotNull(exportedCertificate); + Assert.True(exportedCertificate.HasPrivateKey); + + Assert.Equal(identityTokenSigningCertificates.GetCertHashString(), exportedCertificate.GetCertHashString()); + } + } +} + +#endif \ No newline at end of file diff --git a/src/Shared/test/Shared.Tests/ClosedGenericMatcherTest.cs b/src/Shared/test/Shared.Tests/ClosedGenericMatcherTest.cs new file mode 100644 index 0000000000..e71a792692 --- /dev/null +++ b/src/Shared/test/Shared.Tests/ClosedGenericMatcherTest.cs @@ -0,0 +1,360 @@ +// 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.Collections; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using Xunit; + +namespace Microsoft.Extensions.Internal +{ + public class ClosedGenericMatcherTest + { + // queryType, interfaceType, expectedResult + public static TheoryData ExtractGenericInterfaceDataSet + { + get + { + return new TheoryData + { + // Closed generic types that match given open generic type. + { + typeof(IEnumerable), + typeof(IEnumerable<>), + typeof(IEnumerable) + }, + { + typeof(IReadOnlyList), + typeof(IReadOnlyList<>), + typeof(IReadOnlyList) + }, + { + typeof(KeyValuePair), + typeof(KeyValuePair<,>), + typeof(KeyValuePair) + }, + // Closed generic interfaces that implement sub-interface of given open generic type. + { + typeof(ICollection), + typeof(IEnumerable<>), + typeof(IEnumerable) + }, + { + typeof(IReadOnlyList), + typeof(IEnumerable<>), + typeof(IEnumerable) + }, + { + typeof(IDictionary), + typeof(IEnumerable<>), + typeof(IEnumerable>) + }, + // Class that implements closed generic based on given open generic interface. + { + typeof(BaseClass), + typeof(IDictionary<,>), + typeof(IDictionary) + }, + { + typeof(BaseClass), + typeof(IEquatable<>), + typeof(IEquatable) + }, + { + typeof(BaseClass), + typeof(ICollection<>), + typeof(ICollection>) + }, + // Derived class that implements closed generic based on given open generic interface. + { + typeof(DerivedClass), + typeof(IDictionary<,>), + typeof(IDictionary) + }, + { + typeof(DerivedClass), + typeof(IEquatable<>), + typeof(IEquatable) + }, + { + typeof(DerivedClass), + typeof(ICollection<>), + typeof(ICollection>) + }, + // Derived class that also implements another interface. + { + typeof(DerivedClassWithComparable), + typeof(IDictionary<,>), + typeof(IDictionary) + }, + { + typeof(DerivedClassWithComparable), + typeof(IEquatable<>), + typeof(IEquatable) + }, + { + typeof(DerivedClassWithComparable), + typeof(ICollection<>), + typeof(ICollection>) + }, + { + typeof(DerivedClassWithComparable), + typeof(IComparable<>), + typeof(IComparable) + }, + // Derived class using system implementation. + { + typeof(DerivedClassFromSystemImplementation), + typeof(ICollection<>), + typeof(ICollection) + }, + { + typeof(DerivedClassFromSystemImplementation), + typeof(IReadOnlyList<>), + typeof(IReadOnlyList) + }, + { + typeof(DerivedClassFromSystemImplementation), + typeof(IEnumerable<>), + typeof(IEnumerable) + }, + // Not given an open generic type. + { + typeof(IEnumerable), + typeof(IEnumerable), + null + }, + { + typeof(IEnumerable), + typeof(IEnumerable), + null + }, + { + typeof(IReadOnlyList), + typeof(BaseClass), + null + }, + { + typeof(KeyValuePair<,>), + typeof(KeyValuePair), + null + }, + // Not a match. + { + typeof(IEnumerable), + typeof(IReadOnlyList<>), + null + }, + { + typeof(IList), + typeof(IReadOnlyList<>), + null + }, + { + typeof(IDictionary), + typeof(KeyValuePair<,>), + null + }, + }; + } + } + + [Theory] + [MemberData(nameof(ExtractGenericInterfaceDataSet))] + public void ExtractGenericInterface_ReturnsExpectedType( + Type queryType, + Type interfaceType, + Type expectedResult) + { + // Arrange & Act + var result = ClosedGenericMatcher.ExtractGenericInterface(queryType, interfaceType); + + // Assert + Assert.Equal(expectedResult, result); + } + + // IEnumerable is preferred because it is defined on the more-derived type. + [Fact] + public void ExtractGenericInterface_MultipleDefinitionsInherited() + { + // Arrange + var type = typeof(TwoIEnumerableImplementationsInherited); + + // Act + var result = ClosedGenericMatcher.ExtractGenericInterface(type, typeof(IEnumerable<>)); + + // Sort + Assert.Equal(typeof(IEnumerable), result); + } + + // IEnumerable is preferred because we sort by Ordinal on the full name. + [Fact] + public void ExtractGenericInterface_MultipleDefinitionsOnSameType() + { + // Arrange + var type = typeof(TwoIEnumerableImplementationsOnSameClass); + + // Act + var result = ClosedGenericMatcher.ExtractGenericInterface(type, typeof(IEnumerable<>)); + + // Sort + Assert.Equal(typeof(IEnumerable), result); + } + + private class TwoIEnumerableImplementationsOnSameClass : IEnumerable, IEnumerable + { + IEnumerator IEnumerable.GetEnumerator() + { + throw new NotImplementedException(); + } + + IEnumerator IEnumerable.GetEnumerator() + { + throw new NotImplementedException(); + } + + IEnumerator IEnumerable.GetEnumerator() + { + throw new NotImplementedException(); + } + } + + private class TwoIEnumerableImplementationsInherited : List, IEnumerable + { + IEnumerator IEnumerable.GetEnumerator() + { + throw new NotImplementedException(); + } + + IEnumerator IEnumerable.GetEnumerator() + { + throw new NotImplementedException(); + } + } + + private class BaseClass : IDictionary, IEquatable + { + object IDictionary.this[string key] + { + get + { + throw new NotImplementedException(); + } + + set + { + throw new NotImplementedException(); + } + } + + int ICollection>.Count + { + get + { + throw new NotImplementedException(); + } + } + + bool ICollection>.IsReadOnly + { + get + { + throw new NotImplementedException(); + } + } + + ICollection IDictionary.Keys + { + get + { + throw new NotImplementedException(); + } + } + + ICollection IDictionary.Values + { + get + { + throw new NotImplementedException(); + } + } + + public bool Equals(BaseClass other) + { + throw new NotImplementedException(); + } + + void ICollection>.Add(KeyValuePair item) + { + throw new NotImplementedException(); + } + + void IDictionary.Add(string key, object value) + { + throw new NotImplementedException(); + } + + void ICollection>.Clear() + { + throw new NotImplementedException(); + } + + bool ICollection>.Contains(KeyValuePair item) + { + throw new NotImplementedException(); + } + + bool IDictionary.ContainsKey(string key) + { + throw new NotImplementedException(); + } + + void ICollection>.CopyTo(KeyValuePair[] array, int arrayIndex) + { + throw new NotImplementedException(); + } + + IEnumerator IEnumerable.GetEnumerator() + { + throw new NotImplementedException(); + } + + IEnumerator> IEnumerable>.GetEnumerator() + { + throw new NotImplementedException(); + } + + bool ICollection>.Remove(KeyValuePair item) + { + throw new NotImplementedException(); + } + + bool IDictionary.Remove(string key) + { + throw new NotImplementedException(); + } + + bool IDictionary.TryGetValue(string key, out object value) + { + throw new NotImplementedException(); + } + } + + private class DerivedClass : BaseClass + { + } + + private class DerivedClassWithComparable : DerivedClass, IComparable + { + public int CompareTo(DerivedClassWithComparable other) + { + throw new NotImplementedException(); + } + } + + private class DerivedClassFromSystemImplementation : Collection + { + } + } +} \ No newline at end of file diff --git a/src/Shared/test/Shared.Tests/CommandLineApplicationTests.cs b/src/Shared/test/Shared.Tests/CommandLineApplicationTests.cs new file mode 100644 index 0000000000..44b7ebdaa8 --- /dev/null +++ b/src/Shared/test/Shared.Tests/CommandLineApplicationTests.cs @@ -0,0 +1,693 @@ +// 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.IO; +using System.Linq; +using System.Text; +using Microsoft.Extensions.CommandLineUtils; +using Xunit; + +namespace Microsoft.Extensions.Internal +{ + public class CommandLineApplicationTests + { + [Fact] + public void CommandNameCanBeMatched() + { + var called = false; + + var app = new CommandLineApplication(); + app.Command("test", c => + { + c.OnExecute(() => + { + called = true; + return 5; + }); + }); + + var result = app.Execute("test"); + Assert.Equal(5, result); + Assert.True(called); + } + + [Fact] + public void RemainingArgsArePassed() + { + CommandArgument first = null; + CommandArgument second = null; + + var app = new CommandLineApplication(); + + app.Command("test", c => + { + first = c.Argument("first", "First argument"); + second = c.Argument("second", "Second argument"); + c.OnExecute(() => 0); + }); + + app.Execute("test", "one", "two"); + + Assert.Equal("one", first.Value); + Assert.Equal("two", second.Value); + } + + [Fact] + public void ExtraArgumentCausesException() + { + CommandArgument first = null; + CommandArgument second = null; + + var app = new CommandLineApplication(); + + app.Command("test", c => + { + first = c.Argument("first", "First argument"); + second = c.Argument("second", "Second argument"); + c.OnExecute(() => 0); + }); + + var ex = Assert.Throws(() => app.Execute("test", "one", "two", "three")); + + Assert.Contains("three", ex.Message); + } + + [Fact] + public void UnknownCommandCausesException() + { + var app = new CommandLineApplication(); + + app.Command("test", c => + { + c.Argument("first", "First argument"); + c.Argument("second", "Second argument"); + c.OnExecute(() => 0); + }); + + var ex = Assert.Throws(() => app.Execute("test2", "one", "two", "three")); + + Assert.Contains("test2", ex.Message); + } + + [Fact] + public void MultipleValuesArgumentConsumesAllArgumentValues() + { + CommandArgument argument = null; + + var app = new CommandLineApplication(); + + app.Command("test", c => + { + argument = c.Argument("arg", "Argument that allows multiple values", multipleValues: true); + c.OnExecute(() => 0); + }); + + app.Execute("test", "one", "two", "three", "four", "five"); + + Assert.Equal(new[] { "one", "two", "three", "four", "five" }, argument.Values); + } + + [Fact] + public void MultipleValuesArgumentConsumesAllRemainingArgumentValues() + { + CommandArgument first = null; + CommandArgument second = null; + CommandArgument third = null; + + var app = new CommandLineApplication(); + + app.Command("test", c => + { + first = c.Argument("first", "First argument"); + second = c.Argument("second", "Second argument"); + third = c.Argument("third", "Third argument that allows multiple values", multipleValues: true); + c.OnExecute(() => 0); + }); + + app.Execute("test", "one", "two", "three", "four", "five"); + + Assert.Equal("one", first.Value); + Assert.Equal("two", second.Value); + Assert.Equal(new[] { "three", "four", "five" }, third.Values); + } + + [Fact] + public void MultipleValuesArgumentMustBeTheLastArgument() + { + var app = new CommandLineApplication(); + app.Argument("first", "First argument", multipleValues: true); + var ex = Assert.Throws(() => app.Argument("second", "Second argument")); + + Assert.Contains($"The last argument 'first' accepts multiple values. No more argument can be added.", + ex.Message); + } + + [Fact] + public void OptionSwitchMayBeProvided() + { + CommandOption first = null; + CommandOption second = null; + + var app = new CommandLineApplication(); + + app.Command("test", c => + { + first = c.Option("--first ", "First argument", CommandOptionType.SingleValue); + second = c.Option("--second ", "Second argument", CommandOptionType.SingleValue); + c.OnExecute(() => 0); + }); + + app.Execute("test", "--first", "one", "--second", "two"); + + Assert.Equal("one", first.Values[0]); + Assert.Equal("two", second.Values[0]); + } + + [Fact] + public void OptionValueMustBeProvided() + { + CommandOption first = null; + + var app = new CommandLineApplication(); + + app.Command("test", c => + { + first = c.Option("--first ", "First argument", CommandOptionType.SingleValue); + c.OnExecute(() => 0); + }); + + var ex = Assert.Throws(() => app.Execute("test", "--first")); + + Assert.Contains($"Missing value for option '{first.LongName}'", ex.Message); + } + + [Fact] + public void ValuesMayBeAttachedToSwitch() + { + CommandOption first = null; + CommandOption second = null; + + var app = new CommandLineApplication(); + + app.Command("test", c => + { + first = c.Option("--first ", "First argument", CommandOptionType.SingleValue); + second = c.Option("--second ", "Second argument", CommandOptionType.SingleValue); + c.OnExecute(() => 0); + }); + + app.Execute("test", "--first=one", "--second:two"); + + Assert.Equal("one", first.Values[0]); + Assert.Equal("two", second.Values[0]); + } + + [Fact] + public void ShortNamesMayBeDefined() + { + CommandOption first = null; + CommandOption second = null; + + var app = new CommandLineApplication(); + + app.Command("test", c => + { + first = c.Option("-1 --first ", "First argument", CommandOptionType.SingleValue); + second = c.Option("-2 --second ", "Second argument", CommandOptionType.SingleValue); + c.OnExecute(() => 0); + }); + + app.Execute("test", "-1=one", "-2", "two"); + + Assert.Equal("one", first.Values[0]); + Assert.Equal("two", second.Values[0]); + } + + [Fact] + public void ThrowsExceptionOnUnexpectedCommandOrArgumentByDefault() + { + var unexpectedArg = "UnexpectedArg"; + var app = new CommandLineApplication(); + + app.Command("test", c => + { + c.OnExecute(() => 0); + }); + + var exception = Assert.Throws(() => app.Execute("test", unexpectedArg)); + Assert.Equal($"Unrecognized command or argument '{unexpectedArg}'", exception.Message); + } + + [Fact] + public void AllowNoThrowBehaviorOnUnexpectedArgument() + { + var unexpectedArg = "UnexpectedArg"; + var app = new CommandLineApplication(); + + var testCmd = app.Command("test", c => + { + c.OnExecute(() => 0); + }, + throwOnUnexpectedArg: false); + + // (does not throw) + app.Execute("test", unexpectedArg); + var arg = Assert.Single(testCmd.RemainingArguments); + Assert.Equal(unexpectedArg, arg); + } + + [Fact] + public void ThrowsExceptionOnUnexpectedLongOptionByDefault() + { + var unexpectedOption = "--UnexpectedOption"; + var app = new CommandLineApplication(); + + app.Command("test", c => + { + c.OnExecute(() => 0); + }); + + var exception = Assert.Throws(() => app.Execute("test", unexpectedOption)); + Assert.Equal($"Unrecognized option '{unexpectedOption}'", exception.Message); + } + + [Fact] + public void AllowNoThrowBehaviorOnUnexpectedLongOption() + { + var unexpectedOption = "--UnexpectedOption"; + var app = new CommandLineApplication(); + + var testCmd = app.Command("test", c => + { + c.OnExecute(() => 0); + }, + throwOnUnexpectedArg: false); + + // (does not throw) + app.Execute("test", unexpectedOption); + var arg = Assert.Single(testCmd.RemainingArguments); + Assert.Equal(unexpectedOption, arg); + } + + [Fact] + public void ThrowsExceptionOnUnexpectedShortOptionByDefault() + { + var unexpectedOption = "-uexp"; + var app = new CommandLineApplication(); + + app.Command("test", c => + { + c.OnExecute(() => 0); + }); + + var exception = Assert.Throws(() => app.Execute("test", unexpectedOption)); + Assert.Equal($"Unrecognized option '{unexpectedOption}'", exception.Message); + } + + [Fact] + public void AllowNoThrowBehaviorOnUnexpectedShortOption() + { + var unexpectedOption = "-uexp"; + var app = new CommandLineApplication(); + + var testCmd = app.Command("test", c => + { + c.OnExecute(() => 0); + }, + throwOnUnexpectedArg: false); + + // (does not throw) + app.Execute("test", unexpectedOption); + var arg = Assert.Single(testCmd.RemainingArguments); + Assert.Equal(unexpectedOption, arg); + } + + [Fact] + public void ThrowsExceptionOnUnexpectedSymbolOptionByDefault() + { + var unexpectedOption = "-?"; + var app = new CommandLineApplication(); + + app.Command("test", c => + { + c.OnExecute(() => 0); + }); + + var exception = Assert.Throws(() => app.Execute("test", unexpectedOption)); + Assert.Equal($"Unrecognized option '{unexpectedOption}'", exception.Message); + } + + [Fact] + public void AllowNoThrowBehaviorOnUnexpectedSymbolOption() + { + var unexpectedOption = "-?"; + var app = new CommandLineApplication(); + + var testCmd = app.Command("test", c => + { + c.OnExecute(() => 0); + }, + throwOnUnexpectedArg: false); + + // (does not throw) + app.Execute("test", unexpectedOption); + var arg = Assert.Single(testCmd.RemainingArguments); + Assert.Equal(unexpectedOption, arg); + } + + [Fact] + public void ThrowsExceptionOnUnexpectedOptionBeforeValidSubcommandByDefault() + { + var unexpectedOption = "--unexpected"; + CommandLineApplication subCmd = null; + var app = new CommandLineApplication(); + + app.Command("k", c => + { + subCmd = c.Command("run", _ => { }); + c.OnExecute(() => 0); + }); + + var exception = Assert.Throws(() => app.Execute("k", unexpectedOption, "run")); + Assert.Equal($"Unrecognized option '{unexpectedOption}'", exception.Message); + } + + [Fact] + public void AllowNoThrowBehaviorOnUnexpectedOptionAfterSubcommand() + { + var unexpectedOption = "--unexpected"; + CommandLineApplication subCmd = null; + var app = new CommandLineApplication(); + + var testCmd = app.Command("k", c => + { + subCmd = c.Command("run", _ => { }, throwOnUnexpectedArg: false); + c.OnExecute(() => 0); + }); + + // (does not throw) + app.Execute("k", "run", unexpectedOption); + Assert.Empty(testCmd.RemainingArguments); + var arg = Assert.Single(subCmd.RemainingArguments); + Assert.Equal(unexpectedOption, arg); + } + + [Fact] + public void OptionsCanBeInherited() + { + var app = new CommandLineApplication(); + var optionA = app.Option("-a|--option-a", "", CommandOptionType.SingleValue, inherited: true); + string optionAValue = null; + + var optionB = app.Option("-b", "", CommandOptionType.SingleValue, inherited: false); + + var subcmd = app.Command("subcmd", c => + { + c.OnExecute(() => + { + optionAValue = optionA.Value(); + return 0; + }); + }); + + Assert.Equal(2, app.GetOptions().Count()); + Assert.Single(subcmd.GetOptions()); + + app.Execute("-a", "A1", "subcmd"); + Assert.Equal("A1", optionAValue); + + Assert.Throws(() => app.Execute("subcmd", "-b", "B")); + + Assert.Contains("-a|--option-a", subcmd.GetHelpText()); + } + + [Fact] + public void NestedOptionConflictThrows() + { + var app = new CommandLineApplication(); + app.Option("-a|--always", "Top-level", CommandOptionType.SingleValue, inherited: true); + app.Command("subcmd", c => + { + c.Option("-a|--ask", "Nested", CommandOptionType.SingleValue); + }); + + Assert.Throws(() => app.Execute("subcmd", "-a", "b")); + } + + [Fact] + public void OptionsWithSameName() + { + var app = new CommandLineApplication(); + var top = app.Option("-a|--always", "Top-level", CommandOptionType.SingleValue, inherited: false); + CommandOption nested = null; + app.Command("subcmd", c => + { + nested = c.Option("-a|--ask", "Nested", CommandOptionType.SingleValue); + }); + + app.Execute("-a", "top"); + Assert.Equal("top", top.Value()); + Assert.Null(nested.Value()); + + top.Values.Clear(); + + app.Execute("subcmd", "-a", "nested"); + Assert.Null(top.Value()); + Assert.Equal("nested", nested.Value()); + } + + [Fact] + public void NestedInheritedOptions() + { + string globalOptionValue = null, nest1OptionValue = null, nest2OptionValue = null; + + var app = new CommandLineApplication(); + CommandLineApplication subcmd2 = null; + var g = app.Option("-g|--global", "Global option", CommandOptionType.SingleValue, inherited: true); + var subcmd1 = app.Command("lvl1", s1 => + { + var n1 = s1.Option("--nest1", "Nested one level down", CommandOptionType.SingleValue, inherited: true); + subcmd2 = s1.Command("lvl2", s2 => + { + var n2 = s2.Option("--nest2", "Nested one level down", CommandOptionType.SingleValue, inherited: true); + s2.HelpOption("-h|--help"); + s2.OnExecute(() => + { + globalOptionValue = g.Value(); + nest1OptionValue = n1.Value(); + nest2OptionValue = n2.Value(); + return 0; + }); + }); + }); + + Assert.DoesNotContain(app.GetOptions(), o => o.LongName == "nest2"); + Assert.DoesNotContain(app.GetOptions(), o => o.LongName == "nest1"); + Assert.Contains(app.GetOptions(), o => o.LongName == "global"); + + Assert.DoesNotContain(subcmd1.GetOptions(), o => o.LongName == "nest2"); + Assert.Contains(subcmd1.GetOptions(), o => o.LongName == "nest1"); + Assert.Contains(subcmd1.GetOptions(), o => o.LongName == "global"); + + Assert.Contains(subcmd2.GetOptions(), o => o.LongName == "nest2"); + Assert.Contains(subcmd2.GetOptions(), o => o.LongName == "nest1"); + Assert.Contains(subcmd2.GetOptions(), o => o.LongName == "global"); + + Assert.Throws(() => app.Execute("--nest2", "N2", "--nest1", "N1", "-g", "G")); + Assert.Throws(() => app.Execute("lvl1", "--nest2", "N2", "--nest1", "N1", "-g", "G")); + + app.Execute("lvl1", "lvl2", "--nest2", "N2", "-g", "G", "--nest1", "N1"); + Assert.Equal("G", globalOptionValue); + Assert.Equal("N1", nest1OptionValue); + Assert.Equal("N2", nest2OptionValue); + } + + [Theory] + [InlineData(new string[0], new string[0], null)] + [InlineData(new[] { "--" }, new string[0], null)] + [InlineData(new[] { "-t", "val" }, new string[0], "val")] + [InlineData(new[] { "-t", "val", "--" }, new string[0], "val")] + [InlineData(new[] { "--top", "val", "--", "a" }, new[] { "a" }, "val")] + [InlineData(new[] { "--", "a", "--top", "val" }, new[] { "a", "--top", "val" }, null)] + [InlineData(new[] { "-t", "val", "--", "a", "--", "b" }, new[] { "a", "--", "b" }, "val")] + [InlineData(new[] { "--", "--help" }, new[] { "--help" }, null)] + [InlineData(new[] { "--", "--version" }, new[] { "--version" }, null)] + public void ArgumentSeparator(string[] input, string[] expectedRemaining, string topLevelValue) + { + var app = new CommandLineApplication(throwOnUnexpectedArg: false) + { + AllowArgumentSeparator = true + }; + var optHelp = app.HelpOption("--help"); + var optVersion = app.VersionOption("--version", "1", "1.0"); + var optTop = app.Option("-t|--top ", "arg for command", CommandOptionType.SingleValue); + app.Execute(input); + + Assert.Equal(topLevelValue, optTop.Value()); + Assert.False(optHelp.HasValue()); + Assert.False(optVersion.HasValue()); + Assert.Equal(expectedRemaining, app.RemainingArguments.ToArray()); + } + + [Fact] + public void HelpTextIgnoresHiddenItems() + { + var app = new CommandLineApplication() + { + Name = "ninja-app", + Description = "You can't see it until it is too late" + }; + + app.Command("star", c => + { + c.Option("--points

", "How many", CommandOptionType.MultipleValue); + c.ShowInHelpText = false; + }); + app.Option("--smile", "Be a nice ninja", CommandOptionType.NoValue, o => { o.ShowInHelpText = false; }); + + var a = app.Argument("name", "Pseudonym, of course"); + a.ShowInHelpText = false; + + var help = app.GetHelpText(); + + Assert.Contains("ninja-app", help); + Assert.DoesNotContain("--points", help); + Assert.DoesNotContain("--smile", help); + Assert.DoesNotContain("name", help); + } + + [Fact] + public void HelpTextUsesHelpOptionName() + { + var app = new CommandLineApplication + { + Name = "superhombre" + }; + + app.HelpOption("--ayuda-me"); + var help = app.GetHelpText(); + Assert.Contains("--ayuda-me", help); + } + + [Fact] + public void HelpTextShowsArgSeparator() + { + var app = new CommandLineApplication(throwOnUnexpectedArg: false) + { + Name = "proxy-command", + AllowArgumentSeparator = true + }; + app.HelpOption("-h|--help"); + Assert.Contains("Usage: proxy-command [options] [[--] ...]", app.GetHelpText()); + } + + [Fact] + public void HelpTextShowsExtendedHelp() + { + var app = new CommandLineApplication() + { + Name = "befuddle", + ExtendedHelpText = @" +Remarks: + This command is so confusing that I want to include examples in the help text. + +Examples: + dotnet befuddle -- I Can Haz Confusion Arguments +" + }; + + Assert.Contains(app.ExtendedHelpText, app.GetHelpText()); + } + + [Theory] + [InlineData(new[] { "--version", "--flag" }, "1.0")] + [InlineData(new[] { "-V", "-f" }, "1.0")] + [InlineData(new[] { "--help", "--flag" }, "some flag")] + [InlineData(new[] { "-h", "-f" }, "some flag")] + public void HelpAndVersionOptionStopProcessing(string[] input, string expectedOutData) + { + using (var outWriter = new StringWriter()) + { + var app = new CommandLineApplication { Out = outWriter }; + app.HelpOption("-h --help"); + app.VersionOption("-V --version", "1", "1.0"); + var optFlag = app.Option("-f |--flag", "some flag", CommandOptionType.NoValue); + + app.Execute(input); + + outWriter.Flush(); + var outData = outWriter.ToString(); + Assert.Contains(expectedOutData, outData); + Assert.False(optFlag.HasValue()); + } + } + + // disable inaccurate analyzer error https://github.com/xunit/xunit/issues/1274 +#pragma warning disable xUnit1010 +#pragma warning disable xUnit1011 + [Theory] + [InlineData("-f:File1", "-f:File2")] + [InlineData("--file:File1", "--file:File2")] + [InlineData("--file", "File1", "--file", "File2")] +#pragma warning restore xUnit1010 +#pragma warning restore xUnit1011 + public void ThrowsExceptionOnSingleValueOptionHavingTwoValues(params string[] inputOptions) + { + var app = new CommandLineApplication(); + app.Option("-f |--file", "some file", CommandOptionType.SingleValue); + + var exception = Assert.Throws(() => app.Execute(inputOptions)); + + Assert.Equal("Unexpected value 'File2' for option 'file'", exception.Message); + } + + [Theory] + [InlineData("-v")] + [InlineData("--verbose")] + public void NoValueOptionCanBeSet(string input) + { + var app = new CommandLineApplication(); + var optVerbose = app.Option("-v |--verbose", "be verbose", CommandOptionType.NoValue); + + app.Execute(input); + + Assert.True(optVerbose.HasValue()); + } + + [Theory] + [InlineData("-v:true")] + [InlineData("--verbose:true")] + public void ThrowsExceptionOnNoValueOptionHavingValue(string inputOption) + { + var app = new CommandLineApplication(); + app.Option("-v |--verbose", "be verbose", CommandOptionType.NoValue); + + var exception = Assert.Throws(() => app.Execute(inputOption)); + + Assert.Equal("Unexpected value 'true' for option 'verbose'", exception.Message); + } + + [Fact] + public void ThrowsExceptionOnEmptyCommandOrArgument() + { + var inputOption = String.Empty; + var app = new CommandLineApplication(); + + var exception = Assert.Throws(() => app.Execute(inputOption)); + + Assert.Equal($"Unrecognized command or argument '{inputOption}'", exception.Message); + } + + [Fact] + public void ThrowsExceptionOnInvalidOption() + { + var inputOption = "-"; + var app = new CommandLineApplication(); + + var exception = Assert.Throws(() => app.Execute(inputOption)); + + Assert.Equal($"Unrecognized option '{inputOption}'", exception.Message); + } + } +} diff --git a/src/Shared/test/Shared.Tests/CopyOnWriteDictionaryHolderTest.cs b/src/Shared/test/Shared.Tests/CopyOnWriteDictionaryHolderTest.cs new file mode 100644 index 0000000000..9a0951eb27 --- /dev/null +++ b/src/Shared/test/Shared.Tests/CopyOnWriteDictionaryHolderTest.cs @@ -0,0 +1,91 @@ +// 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.Collections.Generic; +using System.Linq; +using Xunit; + +namespace Microsoft.Extensions.Internal +{ + public class CopyOnWriteDictionaryHolderTest + { + [Fact] + public void ReadOperation_DelegatesToSourceDictionary_IfNoMutationsArePerformed() + { + // Arrange + var source = new Dictionary(StringComparer.OrdinalIgnoreCase) + { + { "test-key", "test-value" }, + { "key2", "key2-value" } + }; + + var holder = new CopyOnWriteDictionaryHolder(source); + + // Act and Assert + Assert.Equal("key2-value", holder["key2"]); + Assert.Equal(2, holder.Count); + Assert.Equal(new string[] { "test-key", "key2" }, holder.Keys.ToArray()); + Assert.Equal(new object[] { "test-value", "key2-value" }, holder.Values.ToArray()); + Assert.True(holder.ContainsKey("test-key")); + + object value; + Assert.False(holder.TryGetValue("different-key", out value)); + + Assert.False(holder.HasBeenCopied); + Assert.Same(source, holder.ReadDictionary); + } + + [Fact] + public void ReadOperation_DoesNotDelegateToSourceDictionary_OnceAValueIsChanged() + { + // Arrange + var source = new Dictionary(StringComparer.OrdinalIgnoreCase) + { + { "key1", "value1" }, + { "key2", "value2" } + }; + + var holder = new CopyOnWriteDictionaryHolder(source); + + // Act + holder["key2"] = "value3"; + + // Assert + Assert.Equal("value2", source["key2"]); + Assert.Equal(2, holder.Count); + Assert.Equal("value1", holder["key1"]); + Assert.Equal("value3", holder["key2"]); + + Assert.True(holder.HasBeenCopied); + Assert.NotSame(source, holder.ReadDictionary); + } + + [Fact] + public void ReadOperation_DoesNotDelegateToSourceDictionary_OnceValueIsAdded() + { + // Arrange + var source = new Dictionary(StringComparer.OrdinalIgnoreCase) + { + { "key1", "value1" }, + { "key2", "value2" } + }; + + var holder = new CopyOnWriteDictionaryHolder(source); + + // Act + holder.Add("key3", "value3"); + holder.Remove("key1"); + + // Assert + Assert.Equal(2, source.Count); + Assert.Equal("value1", source["key1"]); + Assert.Equal(2, holder.Count); + Assert.Equal("value2", holder["KeY2"]); + Assert.Equal("value3", holder["key3"]); + + Assert.True(holder.HasBeenCopied); + Assert.NotSame(source, holder.ReadDictionary); + } + } +} diff --git a/src/Shared/test/Shared.Tests/CopyOnWriteDictionaryTest.cs b/src/Shared/test/Shared.Tests/CopyOnWriteDictionaryTest.cs new file mode 100644 index 0000000000..c1b54036d4 --- /dev/null +++ b/src/Shared/test/Shared.Tests/CopyOnWriteDictionaryTest.cs @@ -0,0 +1,109 @@ +// 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.Collections.Generic; +using Moq; +using Xunit; + +namespace Microsoft.Extensions.Internal +{ + public class CopyOnWriteDictionaryTest + { + [Fact] + public void ReadOperation_DelegatesToSourceDictionary_IfNoMutationsArePerformed() + { + // Arrange + var values = new List(); + var enumerator = Mock.Of>>(); + var sourceDictionary = new Mock>(MockBehavior.Strict); + sourceDictionary + .SetupGet(d => d.Count) + .Returns(100) + .Verifiable(); + sourceDictionary + .SetupGet(d => d.Values) + .Returns(values) + .Verifiable(); + sourceDictionary + .Setup(d => d.ContainsKey("test-key")) + .Returns(value: true) + .Verifiable(); + sourceDictionary + .Setup(d => d.GetEnumerator()) + .Returns(enumerator) + .Verifiable(); + sourceDictionary + .Setup(d => d["key2"]) + .Returns("key2-value") + .Verifiable(); + object value; + sourceDictionary.Setup(d => d.TryGetValue("different-key", out value)) + .Returns(false) + .Verifiable(); + + var copyOnWriteDictionary = new CopyOnWriteDictionary(sourceDictionary.Object, + StringComparer.OrdinalIgnoreCase); + + // Act and Assert + Assert.Equal("key2-value", copyOnWriteDictionary["key2"]); + Assert.Equal(100, copyOnWriteDictionary.Count); + Assert.Same(values, copyOnWriteDictionary.Values); + Assert.True(copyOnWriteDictionary.ContainsKey("test-key")); + Assert.Same(enumerator, copyOnWriteDictionary.GetEnumerator()); + Assert.False(copyOnWriteDictionary.TryGetValue("different-key", out value)); + sourceDictionary.Verify(); + } + + [Fact] + public void ReadOperation_DoesNotDelegateToSourceDictionary_OnceAValueIsChanged() + { + // Arrange + var values = new List(); + var sourceDictionary = new Dictionary + { + { "key1", "value1" }, + { "key2", "value2" } + }; + var copyOnWriteDictionary = new CopyOnWriteDictionary( + sourceDictionary, + StringComparer.OrdinalIgnoreCase); + + // Act + copyOnWriteDictionary["key2"] = "value3"; + + // Assert + Assert.Equal("value2", sourceDictionary["key2"]); + Assert.Equal(2, copyOnWriteDictionary.Count); + Assert.Equal("value1", copyOnWriteDictionary["key1"]); + Assert.Equal("value3", copyOnWriteDictionary["key2"]); + } + + [Fact] + public void ReadOperation_DoesNotDelegateToSourceDictionary_OnceDictionaryIsModified() + { + // Arrange + var values = new List(); + var sourceDictionary = new Dictionary + { + { "key1", "value1" }, + { "key2", "value2" } + }; + var copyOnWriteDictionary = new CopyOnWriteDictionary( + sourceDictionary, + StringComparer.OrdinalIgnoreCase); + + // Act + copyOnWriteDictionary.Add("key3", "value3"); + copyOnWriteDictionary.Remove("key1"); + + + // Assert + Assert.Equal(2, sourceDictionary.Count); + Assert.Equal("value1", sourceDictionary["key1"]); + Assert.Equal(2, copyOnWriteDictionary.Count); + Assert.Equal("value2", copyOnWriteDictionary["KeY2"]); + Assert.Equal("value3", copyOnWriteDictionary["key3"]); + } + } +} diff --git a/src/Shared/test/Shared.Tests/DotNetMuxerTests.cs b/src/Shared/test/Shared.Tests/DotNetMuxerTests.cs new file mode 100644 index 0000000000..ba1dd06511 --- /dev/null +++ b/src/Shared/test/Shared.Tests/DotNetMuxerTests.cs @@ -0,0 +1,24 @@ +// 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. + +#if NETCOREAPP2_0 || NETCOREAPP2_1 +using System.IO; +using System.Runtime.InteropServices; +using Xunit; + +namespace Microsoft.Extensions.CommandLineUtils +{ + public class DotNetMuxerTests + { + [Fact] + public void FindsTheMuxer() + { + var muxerPath = DotNetMuxer.MuxerPath; + Assert.NotNull(muxerPath); + Assert.True(File.Exists(muxerPath), "The file did not exist"); + Assert.True(Path.IsPathRooted(muxerPath), "The path should be rooted"); + Assert.Equal("dotnet", Path.GetFileNameWithoutExtension(muxerPath), ignoreCase: true); + } + } +} +#endif diff --git a/src/Shared/test/Shared.Tests/HashCodeCombinerTest.cs b/src/Shared/test/Shared.Tests/HashCodeCombinerTest.cs new file mode 100644 index 0000000000..fab3e30979 --- /dev/null +++ b/src/Shared/test/Shared.Tests/HashCodeCombinerTest.cs @@ -0,0 +1,39 @@ +// 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 Xunit; + +namespace Microsoft.Extensions.Internal +{ + public class HashCodeCombinerTest + { + [Fact] + public void GivenTheSameInputs_ItProducesTheSameOutput() + { + var hashCode1 = new HashCodeCombiner(); + var hashCode2 = new HashCodeCombiner(); + + hashCode1.Add(42); + hashCode1.Add("foo"); + hashCode2.Add(42); + hashCode2.Add("foo"); + + Assert.Equal(hashCode1.CombinedHash, hashCode2.CombinedHash); + } + + [Fact] + public void HashCode_Is_OrderSensitive() + { + var hashCode1 = HashCodeCombiner.Start(); + var hashCode2 = HashCodeCombiner.Start(); + + hashCode1.Add(42); + hashCode1.Add("foo"); + + hashCode2.Add("foo"); + hashCode2.Add(42); + + Assert.NotEqual(hashCode1.CombinedHash, hashCode2.CombinedHash); + } + } +} diff --git a/src/Shared/test/Shared.Tests/Microsoft.AspNetCore.Shared.Tests.csproj b/src/Shared/test/Shared.Tests/Microsoft.AspNetCore.Shared.Tests.csproj new file mode 100644 index 0000000000..05b321a6d6 --- /dev/null +++ b/src/Shared/test/Shared.Tests/Microsoft.AspNetCore.Shared.Tests.csproj @@ -0,0 +1,32 @@ + + + + $(StandardTestTfms) + portable + true + + + + + + + + + + + + + + + + + + + + + diff --git a/src/Shared/test/Shared.Tests/ObjectMethodExecutorTest.cs b/src/Shared/test/Shared.Tests/ObjectMethodExecutorTest.cs new file mode 100644 index 0000000000..1c26ef1de1 --- /dev/null +++ b/src/Shared/test/Shared.Tests/ObjectMethodExecutorTest.cs @@ -0,0 +1,634 @@ +// 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 Microsoft.FSharp.Control; +using Microsoft.FSharp.Core; +using System; +using System.Collections.Generic; +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Threading; +using System.Threading.Tasks; +using Xunit; + +namespace Microsoft.Extensions.Internal +{ + public class ObjectMethodExecutorTest + { + private TestObject _targetObject = new TestObject(); + private TypeInfo targetTypeInfo = typeof(TestObject).GetTypeInfo(); + + [Fact] + public void ExecuteValueMethod() + { + var executor = GetExecutorForMethod("ValueMethod"); + var result = executor.Execute( + _targetObject, + new object[] { 10, 20 }); + Assert.False(executor.IsMethodAsync); + Assert.Equal(30, (int)result); + } + + [Fact] + public void ExecuteVoidValueMethod() + { + var executor = GetExecutorForMethod("VoidValueMethod"); + var result = executor.Execute( + _targetObject, + new object[] { 10 }); + Assert.False(executor.IsMethodAsync); + Assert.Null(result); + } + + [Fact] + public void ExecuteValueMethodWithReturnType() + { + var executor = GetExecutorForMethod("ValueMethodWithReturnType"); + var result = executor.Execute( + _targetObject, + new object[] { 10 }); + var resultObject = Assert.IsType(result); + Assert.False(executor.IsMethodAsync); + Assert.Equal("Hello", resultObject.value); + } + + [Fact] + public void ExecuteValueMethodUpdateValue() + { + var executor = GetExecutorForMethod("ValueMethodUpdateValue"); + var parameter = new TestObject(); + var result = executor.Execute( + _targetObject, + new object[] { parameter }); + var resultObject = Assert.IsType(result); + Assert.False(executor.IsMethodAsync); + Assert.Equal("HelloWorld", resultObject.value); + } + + [Fact] + public void ExecuteValueMethodWithReturnTypeThrowsException() + { + var executor = GetExecutorForMethod("ValueMethodWithReturnTypeThrowsException"); + var parameter = new TestObject(); + Assert.False(executor.IsMethodAsync); + Assert.Throws( + () => executor.Execute( + _targetObject, + new object[] { parameter })); + } + + [Fact] + public async Task ExecuteValueMethodAsync() + { + var executor = GetExecutorForMethod("ValueMethodAsync"); + var result = await executor.ExecuteAsync( + _targetObject, + new object[] { 10, 20 }); + Assert.True(executor.IsMethodAsync); + Assert.Equal(30, (int)result); + } + + [Fact] + public async Task ExecuteValueMethodWithReturnTypeAsync() + { + var executor = GetExecutorForMethod("ValueMethodWithReturnTypeAsync"); + var result = await executor.ExecuteAsync( + _targetObject, + new object[] { 10 }); + var resultObject = Assert.IsType(result); + Assert.True(executor.IsMethodAsync); + Assert.Equal("Hello", resultObject.value); + } + + [Fact] + public async Task ExecuteValueMethodUpdateValueAsync() + { + var executor = GetExecutorForMethod("ValueMethodUpdateValueAsync"); + var parameter = new TestObject(); + var result = await executor.ExecuteAsync( + _targetObject, + new object[] { parameter }); + var resultObject = Assert.IsType(result); + Assert.True(executor.IsMethodAsync); + Assert.Equal("HelloWorld", resultObject.value); + } + + [Fact] + public async Task ExecuteValueMethodWithReturnTypeThrowsExceptionAsync() + { + var executor = GetExecutorForMethod("ValueMethodWithReturnTypeThrowsExceptionAsync"); + var parameter = new TestObject(); + Assert.True(executor.IsMethodAsync); + await Assert.ThrowsAsync( + async () => await executor.ExecuteAsync( + _targetObject, + new object[] { parameter })); + } + + [Fact] + public async Task ExecuteValueMethodWithReturnVoidThrowsExceptionAsync() + { + var executor = GetExecutorForMethod("ValueMethodWithReturnVoidThrowsExceptionAsync"); + var parameter = new TestObject(); + Assert.True(executor.IsMethodAsync); + await Assert.ThrowsAsync( + async () => await executor.ExecuteAsync( + _targetObject, + new object[] { parameter })); + } + + [Fact] + public void GetDefaultValueForParameters_ReturnsSuppliedValues() + { + var suppliedDefaultValues = new object[] { 123, "test value" }; + var executor = GetExecutorForMethod("MethodWithMultipleParameters", suppliedDefaultValues); + Assert.Equal(suppliedDefaultValues[0], executor.GetDefaultValueForParameter(0)); + Assert.Equal(suppliedDefaultValues[1], executor.GetDefaultValueForParameter(1)); + Assert.Throws(() => executor.GetDefaultValueForParameter(2)); + } + + [Fact] + public void GetDefaultValueForParameters_ThrowsIfNoneWereSupplied() + { + var executor = GetExecutorForMethod("MethodWithMultipleParameters"); + Assert.Throws(() => executor.GetDefaultValueForParameter(0)); + } + + [Fact] + public async void TargetMethodReturningCustomAwaitableOfReferenceType_CanInvokeViaExecute() + { + // Arrange + var executor = GetExecutorForMethod("CustomAwaitableOfReferenceTypeAsync"); + + // Act + var result = await (TestAwaitable)executor.Execute(_targetObject, new object[] { "Hello", 123 }); + + // Assert + Assert.True(executor.IsMethodAsync); + Assert.Same(typeof(TestObject), executor.AsyncResultType); + Assert.NotNull(result); + Assert.Equal("Hello 123", result.value); + } + + [Fact] + public async void TargetMethodReturningCustomAwaitableOfValueType_CanInvokeViaExecute() + { + // Arrange + var executor = GetExecutorForMethod("CustomAwaitableOfValueTypeAsync"); + + // Act + var result = await (TestAwaitable)executor.Execute(_targetObject, new object[] { 123, 456 }); + + // Assert + Assert.True(executor.IsMethodAsync); + Assert.Same(typeof(int), executor.AsyncResultType); + Assert.Equal(579, result); + } + + [Fact] + public async void TargetMethodReturningCustomAwaitableOfReferenceType_CanInvokeViaExecuteAsync() + { + // Arrange + var executor = GetExecutorForMethod("CustomAwaitableOfReferenceTypeAsync"); + + // Act + var result = await executor.ExecuteAsync(_targetObject, new object[] { "Hello", 123 }); + + // Assert + Assert.True(executor.IsMethodAsync); + Assert.Same(typeof(TestObject), executor.AsyncResultType); + Assert.NotNull(result); + Assert.IsType(result); + Assert.Equal("Hello 123", ((TestObject)result).value); + } + + [Fact] + public async void TargetMethodReturningCustomAwaitableOfValueType_CanInvokeViaExecuteAsync() + { + // Arrange + var executor = GetExecutorForMethod("CustomAwaitableOfValueTypeAsync"); + + // Act + var result = await executor.ExecuteAsync(_targetObject, new object[] { 123, 456 }); + + // Assert + Assert.True(executor.IsMethodAsync); + Assert.Same(typeof(int), executor.AsyncResultType); + Assert.NotNull(result); + Assert.IsType(result); + Assert.Equal(579, (int)result); + } + + [Fact] + public async void TargetMethodReturningAwaitableOfVoidType_CanInvokeViaExecuteAsync() + { + // Arrange + var executor = GetExecutorForMethod("VoidValueMethodAsync"); + + // Act + var result = await executor.ExecuteAsync(_targetObject, new object[] { 123 }); + + // Assert + Assert.True(executor.IsMethodAsync); + Assert.Same(typeof(void), executor.AsyncResultType); + Assert.Null(result); + } + + [Fact] + public async void TargetMethodReturningAwaitableWithICriticalNotifyCompletion_UsesUnsafeOnCompleted() + { + // Arrange + var executor = GetExecutorForMethod("CustomAwaitableWithICriticalNotifyCompletion"); + + // Act + var result = await executor.ExecuteAsync(_targetObject, new object[0]); + + // Assert + Assert.True(executor.IsMethodAsync); + Assert.Same(typeof(string), executor.AsyncResultType); + Assert.Equal("Used UnsafeOnCompleted", (string)result); + } + + [Fact] + public async void TargetMethodReturningAwaitableWithoutICriticalNotifyCompletion_UsesOnCompleted() + { + // Arrange + var executor = GetExecutorForMethod("CustomAwaitableWithoutICriticalNotifyCompletion"); + + // Act + var result = await executor.ExecuteAsync(_targetObject, new object[0]); + + // Assert + Assert.True(executor.IsMethodAsync); + Assert.Same(typeof(string), executor.AsyncResultType); + Assert.Equal("Used OnCompleted", (string)result); + } + + [Fact] + public async void TargetMethodReturningValueTaskOfValueType_CanBeInvokedViaExecute() + { + // Arrange + var executor = GetExecutorForMethod("ValueTaskOfValueType"); + + // Act + var result = await (ValueTask)executor.Execute(_targetObject, new object[] { 123 }); + + // Assert + Assert.True(executor.IsMethodAsync); + Assert.Same(typeof(int), executor.AsyncResultType); + Assert.Equal(123, result); + } + + [Fact] + public async void TargetMethodReturningValueTaskOfReferenceType_CanBeInvokedViaExecute() + { + // Arrange + var executor = GetExecutorForMethod("ValueTaskOfReferenceType"); + + // Act + var result = await (ValueTask)executor.Execute(_targetObject, new object[] { "test result" }); + + // Assert + Assert.True(executor.IsMethodAsync); + Assert.Same(typeof(string), executor.AsyncResultType); + Assert.Equal("test result", result); + } + + [Fact] + public async void TargetMethodReturningValueTaskOfValueType_CanBeInvokedViaExecuteAsync() + { + // Arrange + var executor = GetExecutorForMethod("ValueTaskOfValueType"); + + // Act + var result = await executor.ExecuteAsync(_targetObject, new object[] { 123 }); + + // Assert + Assert.True(executor.IsMethodAsync); + Assert.Same(typeof(int), executor.AsyncResultType); + Assert.NotNull(result); + Assert.Equal(123, (int)result); + } + + [Fact] + public async void TargetMethodReturningValueTaskOfReferenceType_CanBeInvokedViaExecuteAsync() + { + // Arrange + var executor = GetExecutorForMethod("ValueTaskOfReferenceType"); + + // Act + var result = await executor.ExecuteAsync(_targetObject, new object[] { "test result" }); + + // Assert + Assert.True(executor.IsMethodAsync); + Assert.Same(typeof(string), executor.AsyncResultType); + Assert.Equal("test result", result); + } + + [Fact] + public async void TargetMethodReturningFSharpAsync_CanBeInvokedViaExecute() + { + // Arrange + var executor = GetExecutorForMethod("FSharpAsyncMethod"); + + // Act + var fsharpAsync = (FSharpAsync)executor.Execute(_targetObject, new object[] { "test result" }); + var result = await FSharpAsync.StartAsTask(fsharpAsync, + FSharpOption.None, + FSharpOption.None); + + // Assert + Assert.True(executor.IsMethodAsync); + Assert.Same(typeof(string), executor.AsyncResultType); + Assert.Equal("test result", result); + } + + [Fact] + public async void TargetMethodReturningFailingFSharpAsync_CanBeInvokedViaExecute() + { + // Arrange + var executor = GetExecutorForMethod("FSharpAsyncFailureMethod"); + + // Act + var fsharpAsync = (FSharpAsync)executor.Execute(_targetObject, new object[] { "test result" }); + var resultTask = FSharpAsync.StartAsTask(fsharpAsync, + FSharpOption.None, + FSharpOption.None); + + // Assert + Assert.True(executor.IsMethodAsync); + Assert.Same(typeof(string), executor.AsyncResultType); + + var exception = await Assert.ThrowsAsync(async () => await resultTask); + Assert.IsType(exception.InnerException); + Assert.Equal("Test exception", exception.InnerException.Message); + } + + [Fact] + public async void TargetMethodReturningFSharpAsync_CanBeInvokedViaExecuteAsync() + { + // Arrange + var executor = GetExecutorForMethod("FSharpAsyncMethod"); + + // Act + var result = await executor.ExecuteAsync(_targetObject, new object[] { "test result" }); + + // Assert + Assert.True(executor.IsMethodAsync); + Assert.Same(typeof(string), executor.AsyncResultType); + Assert.Equal("test result", result); + } + + [Fact] + public async void TargetMethodReturningFailingFSharpAsync_CanBeInvokedViaExecuteAsync() + { + // Arrange + var executor = GetExecutorForMethod("FSharpAsyncFailureMethod"); + + // Act + var resultTask = executor.ExecuteAsync(_targetObject, new object[] { "test result" }); + + // Assert + Assert.True(executor.IsMethodAsync); + Assert.Same(typeof(string), executor.AsyncResultType); + + var exception = await Assert.ThrowsAsync(async () => await resultTask); + Assert.IsType(exception.InnerException); + Assert.Equal("Test exception", exception.InnerException.Message); + } + + private ObjectMethodExecutor GetExecutorForMethod(string methodName) + { + var method = typeof(TestObject).GetMethod(methodName); + return ObjectMethodExecutor.Create(method, targetTypeInfo); + } + + private ObjectMethodExecutor GetExecutorForMethod(string methodName, object[] parameterDefaultValues) + { + var method = typeof(TestObject).GetMethod(methodName); + return ObjectMethodExecutor.Create(method, targetTypeInfo, parameterDefaultValues); + } + + public class TestObject + { + public string value; + public int ValueMethod(int i, int j) + { + return i + j; + } + + public void VoidValueMethod(int i) + { + + } + + public TestObject ValueMethodWithReturnType(int i) + { + return new TestObject() { value = "Hello" }; ; + } + + public TestObject ValueMethodWithReturnTypeThrowsException(TestObject i) + { + throw new NotImplementedException("Not Implemented Exception"); + } + + public TestObject ValueMethodUpdateValue(TestObject parameter) + { + parameter.value = "HelloWorld"; + return parameter; + } + + public Task ValueMethodAsync(int i, int j) + { + return Task.FromResult(i + j); + } + + public async Task VoidValueMethodAsync(int i) + { + await ValueMethodAsync(3, 4); + } + public Task ValueMethodWithReturnTypeAsync(int i) + { + return Task.FromResult(new TestObject() { value = "Hello" }); + } + + public async Task ValueMethodWithReturnVoidThrowsExceptionAsync(TestObject i) + { + await Task.CompletedTask; + throw new NotImplementedException("Not Implemented Exception"); + } + + public async Task ValueMethodWithReturnTypeThrowsExceptionAsync(TestObject i) + { + await Task.CompletedTask; + throw new NotImplementedException("Not Implemented Exception"); + } + + public Task ValueMethodUpdateValueAsync(TestObject parameter) + { + parameter.value = "HelloWorld"; + return Task.FromResult(parameter); + } + + public TestAwaitable CustomAwaitableOfReferenceTypeAsync( + string input1, + int input2) + { + return new TestAwaitable(new TestObject + { + value = $"{input1} {input2}" + }); + } + + public TestAwaitable CustomAwaitableOfValueTypeAsync( + int input1, + int input2) + { + return new TestAwaitable(input1 + input2); + } + + public TestAwaitableWithICriticalNotifyCompletion CustomAwaitableWithICriticalNotifyCompletion() + { + return new TestAwaitableWithICriticalNotifyCompletion(); + } + + public TestAwaitableWithoutICriticalNotifyCompletion CustomAwaitableWithoutICriticalNotifyCompletion() + { + return new TestAwaitableWithoutICriticalNotifyCompletion(); + } + + public ValueTask ValueTaskOfValueType(int result) + { + return new ValueTask(result); + } + + public ValueTask ValueTaskOfReferenceType(string result) + { + return new ValueTask(result); + } + + public void MethodWithMultipleParameters(int valueTypeParam, string referenceTypeParam) + { + } + + public FSharpAsync FSharpAsyncMethod(string parameter) + { + return FSharpAsync.AwaitTask(Task.FromResult(parameter)); + } + + public FSharpAsync FSharpAsyncFailureMethod(string parameter) + { + return FSharpAsync.AwaitTask( + Task.FromException(new InvalidOperationException("Test exception"))); + } + } + + public class TestAwaitable + { + private T _result; + private bool _isCompleted; + private List _onCompletedCallbacks = new List(); + + public TestAwaitable(T result) + { + _result = result; + + // Simulate a brief delay before completion + ThreadPool.QueueUserWorkItem(_ => + { + Thread.Sleep(100); + SetCompleted(); + }); + } + + private void SetCompleted() + { + _isCompleted = true; + + foreach (var callback in _onCompletedCallbacks) + { + callback(); + } + } + + public TestAwaiter GetAwaiter() + { + return new TestAwaiter(this); + } + + public struct TestAwaiter : INotifyCompletion + { + private TestAwaitable _owner; + + public TestAwaiter(TestAwaitable owner) : this() + { + _owner = owner; + } + + public bool IsCompleted => _owner._isCompleted; + + public void OnCompleted(Action continuation) + { + if (_owner._isCompleted) + { + continuation(); + } + else + { + _owner._onCompletedCallbacks.Add(continuation); + } + } + + public T GetResult() + { + return _owner._result; + } + } + } + + public class TestAwaitableWithICriticalNotifyCompletion + { + public TestAwaiterWithICriticalNotifyCompletion GetAwaiter() + => new TestAwaiterWithICriticalNotifyCompletion(); + } + + public class TestAwaitableWithoutICriticalNotifyCompletion + { + public TestAwaiterWithoutICriticalNotifyCompletion GetAwaiter() + => new TestAwaiterWithoutICriticalNotifyCompletion(); + } + + public class TestAwaiterWithICriticalNotifyCompletion + : CompletionTrackingAwaiterBase, ICriticalNotifyCompletion + { + } + + public class TestAwaiterWithoutICriticalNotifyCompletion + : CompletionTrackingAwaiterBase, INotifyCompletion + { + } + + public class CompletionTrackingAwaiterBase + { + private string _result; + + public bool IsCompleted { get; private set; } + + public string GetResult() => _result; + + public void OnCompleted(Action continuation) + { + _result = "Used OnCompleted"; + IsCompleted = true; + continuation(); + } + + public void UnsafeOnCompleted(Action continuation) + { + _result = "Used UnsafeOnCompleted"; + IsCompleted = true; + continuation(); + } + } + } +} diff --git a/src/Shared/test/Shared.Tests/PropertyActivatorTest.cs b/src/Shared/test/Shared.Tests/PropertyActivatorTest.cs new file mode 100644 index 0000000000..a5cb1605b3 --- /dev/null +++ b/src/Shared/test/Shared.Tests/PropertyActivatorTest.cs @@ -0,0 +1,187 @@ +// 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.Reflection; +using Xunit; + +namespace Microsoft.Extensions.Internal +{ + public class PropertyActivatorTest + { + [Fact] + public void Activate_InvokesValueAccessorWithExpectedValue() + { + // Arrange + var instance = new TestClass(); + var typeInfo = instance.GetType().GetTypeInfo(); + var property = typeInfo.GetDeclaredProperty("IntProperty"); + var invokedWith = -1; + var activator = new PropertyActivator( + property, + valueAccessor: (val) => + { + invokedWith = val; + return val; + }); + + // Act + activator.Activate(instance, 123); + + // Assert + Assert.Equal(123, invokedWith); + } + + [Fact] + public void Activate_SetsPropertyValue() + { + // Arrange + var instance = new TestClass(); + var typeInfo = instance.GetType().GetTypeInfo(); + var property = typeInfo.GetDeclaredProperty("IntProperty"); + var activator = new PropertyActivator(property, valueAccessor: (val) => val + 1); + + // Act + activator.Activate(instance, 123); + + // Assert + Assert.Equal(124, instance.IntProperty); + } + + [Fact] + public void GetPropertiesToActivate_RestrictsActivatableProperties() + { + // Arrange + var instance = new TestClass(); + var typeInfo = instance.GetType().GetTypeInfo(); + var expectedPropertyInfo = typeInfo.GetDeclaredProperty("ActivatableProperty"); + + // Act + var propertiesToActivate = PropertyActivator.GetPropertiesToActivate( + type: typeof(TestClass), + activateAttributeType: typeof(TestActivateAttribute), + createActivateInfo: + (propertyInfo) => new PropertyActivator(propertyInfo, valueAccessor: (val) => val + 1)); + + // Assert + Assert.Collection( + propertiesToActivate, + (activator) => + { + Assert.Equal(expectedPropertyInfo, activator.PropertyInfo); + }); + } + + [Fact] + public void GetPropertiesToActivate_CanCreateCustomPropertyActivators() + { + // Arrange + var instance = new TestClass(); + var typeInfo = instance.GetType().GetTypeInfo(); + var expectedPropertyInfo = typeInfo.GetDeclaredProperty("IntProperty"); + + // Act + var propertiesToActivate = PropertyActivator.GetPropertiesToActivate( + type: typeof(TestClass), + activateAttributeType: typeof(TestActivateAttribute), + createActivateInfo: + (propertyInfo) => new PropertyActivator(expectedPropertyInfo, valueAccessor: (val) => val + 1)); + + // Assert + Assert.Collection( + propertiesToActivate, + (activator) => + { + Assert.Equal(expectedPropertyInfo, activator.PropertyInfo); + }); + } + + [Fact] + public void GetPropertiesToActivate_ExcludesNonPublic() + { + // Arrange + var instance = new TestClassWithPropertyVisiblity(); + var typeInfo = instance.GetType().GetTypeInfo(); + var expectedPropertyInfo = typeInfo.GetDeclaredProperty("Public"); + + // Act + var propertiesToActivate = PropertyActivator.GetPropertiesToActivate( + typeof(TestClassWithPropertyVisiblity), + typeof(TestActivateAttribute), + (propertyInfo) => new PropertyActivator(propertyInfo, valueAccessor: (val) => val)); + + // Assert + Assert.Single(propertiesToActivate); + Assert.Single(propertiesToActivate, p => p.PropertyInfo == expectedPropertyInfo); + } + + [Fact] + public void GetPropertiesToActivate_IncludesNonPublic() + { + // Arrange + var instance = new TestClassWithPropertyVisiblity(); + var typeInfo = instance.GetType().GetTypeInfo(); + + // Act + var propertiesToActivate = PropertyActivator.GetPropertiesToActivate( + typeof(TestClassWithPropertyVisiblity), + typeof(TestActivateAttribute), + (propertyInfo) => new PropertyActivator(propertyInfo, valueAccessor: (val) => val), + includeNonPublic: true); + + // Assert + Assert.Equal(5, propertiesToActivate.Length); + } + + private class TestClass + { + public int IntProperty { get; set; } + + [TestActivate] + public int ActivatableProperty { get; set; } + + [TestActivate] + public int NoSetterActivatableProperty { get; } + + [TestActivate] + public int this[int something] // Not activatable + { + get + { + return 0; + } + } + + [TestActivate] + public static int StaticActivatablProperty { get; set; } + } + + private class TestClassWithPropertyVisiblity + { + [TestActivate] + public int Public { get; set; } + + [TestActivate] + protected int Protected { get; set; } + + [TestActivate] + internal int Internal { get; set; } + + [TestActivate] + protected internal int ProtectedInternal {get; set; } + + [TestActivate] + private int Private { get; set; } + } + + [AttributeUsage(AttributeTargets.Property)] + private class TestActivateAttribute : Attribute + { + } + + private class ActivationInfo + { + public string Name { get; set; } + } + } +} diff --git a/src/Shared/test/Shared.Tests/PropertyHelperTest.cs b/src/Shared/test/Shared.Tests/PropertyHelperTest.cs new file mode 100644 index 0000000000..19cf08b370 --- /dev/null +++ b/src/Shared/test/Shared.Tests/PropertyHelperTest.cs @@ -0,0 +1,831 @@ +// 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.Collections.Generic; +using System.Linq; +using System.Reflection; +using Xunit; + +namespace Microsoft.Extensions.Internal +{ + public class PropertyHelperTest + { + [Fact] + public void PropertyHelper_ReturnsNameCorrectly() + { + // Arrange + var anonymous = new { foo = "bar" }; + var property = PropertyHelper.GetProperties(anonymous.GetType()).First().Property; + + // Act + var helper = new PropertyHelper(property); + + // Assert + Assert.Equal("foo", property.Name); + Assert.Equal("foo", helper.Name); + } + + [Fact] + public void PropertyHelper_ReturnsValueCorrectly() + { + // Arrange + var anonymous = new { bar = "baz" }; + var property = PropertyHelper.GetProperties(anonymous.GetType()).First().Property; + + // Act + var helper = new PropertyHelper(property); + + // Assert + Assert.Equal("bar", helper.Name); + Assert.Equal("baz", helper.GetValue(anonymous)); + } + + [Fact] + public void PropertyHelper_ReturnsGetterDelegate() + { + // Arrange + var anonymous = new { bar = "baz" }; + var property = PropertyHelper.GetProperties(anonymous.GetType()).First().Property; + + // Act + var helper = new PropertyHelper(property); + + // Assert + Assert.NotNull(helper.ValueGetter); + Assert.Equal("baz", helper.ValueGetter(anonymous)); + } + + [Fact] + public void SetValue_SetsPropertyValue() + { + // Arrange + var expected = "new value"; + var instance = new BaseClass { PropA = "old value" }; + var helper = PropertyHelper.GetProperties( + instance.GetType()).First(prop => prop.Name == "PropA"); + + // Act + helper.SetValue(instance, expected); + + // Assert + Assert.Equal(expected, instance.PropA); + } + + [Fact] + public void PropertyHelper_ReturnsSetterDelegate() + { + // Arrange + var expected = "new value"; + var instance = new BaseClass { PropA = "old value" }; + var helper = PropertyHelper.GetProperties( + instance.GetType()).First(prop => prop.Name == "PropA"); + + // Act and Assert + Assert.NotNull(helper.ValueSetter); + helper.ValueSetter(instance, expected); + + // Assert + Assert.Equal(expected, instance.PropA); + } + + [Fact] + public void PropertyHelper_ReturnsValueCorrectly_ForValueTypes() + { + // Arrange + var anonymous = new { foo = 32 }; + var property = PropertyHelper.GetProperties(anonymous.GetType()).First().Property; + + // Act + var helper = new PropertyHelper(property); + + // Assert + Assert.Equal("foo", helper.Name); + Assert.Equal(32, helper.GetValue(anonymous)); + } + + [Fact] + public void PropertyHelper_ReturnsCachedPropertyHelper() + { + // Arrange + var anonymous = new { foo = "bar" }; + + // Act + var helpers1 = PropertyHelper.GetProperties(anonymous.GetType().GetTypeInfo()); + var helpers2 = PropertyHelper.GetProperties(anonymous.GetType().GetTypeInfo()); + + // Assert + Assert.Single(helpers1); + Assert.Same(helpers1, helpers2); + Assert.Same(helpers1[0], helpers2[0]); + } + + [Fact] + public void PropertyHelper_DoesNotChangeUnderscores() + { + // Arrange + var anonymous = new { bar_baz2 = "foo" }; + + // Act + Assert + var helper = Assert.Single(PropertyHelper.GetProperties(anonymous.GetType().GetTypeInfo())); + Assert.Equal("bar_baz2", helper.Name); + } + + [Fact] + public void PropertyHelper_DoesNotFindPrivateProperties() + { + // Arrange + var anonymous = new PrivateProperties(); + + // Act + Assert + var helper = Assert.Single(PropertyHelper.GetProperties(anonymous.GetType().GetTypeInfo())); + Assert.Equal("Prop1", helper.Name); + } + + [Fact] + public void PropertyHelper_DoesNotFindStaticProperties() + { + // Arrange + var anonymous = new Static(); + + // Act + Assert + var helper = Assert.Single(PropertyHelper.GetProperties(anonymous.GetType().GetTypeInfo())); + Assert.Equal("Prop5", helper.Name); + } + + [Fact] + public void PropertyHelper_DoesNotFindSetOnlyProperties() + { + // Arrange + var anonymous = new SetOnly(); + + // Act + Assert + var helper = Assert.Single(PropertyHelper.GetProperties(anonymous.GetType().GetTypeInfo())); + Assert.Equal("Prop6", helper.Name); + } + + [Theory] + [InlineData(typeof(int?))] + [InlineData(typeof(DayOfWeek?))] + public void PropertyHelper_WorksForNullablePrimitiveAndEnumTypes(Type nullableType) + { + // Act + var properties = PropertyHelper.GetProperties(nullableType); + + // Assert + Assert.Empty(properties); + } + + [Fact] + public void PropertyHelper_UnwrapsNullableTypes() + { + // Arrange + var myType = typeof(MyStruct?); + + // Act + var properties = PropertyHelper.GetProperties(myType); + + // Assert + var property = Assert.Single(properties); + Assert.Equal("Foo", property.Name); + } + + [Fact] + public void PropertyHelper_WorksForStruct() + { + // Arrange + var anonymous = new MyProperties(); + + anonymous.IntProp = 3; + anonymous.StringProp = "Five"; + + // Act + Assert + var helper1 = Assert.Single(PropertyHelper.GetProperties(anonymous.GetType().GetTypeInfo()).Where(prop => prop.Name == "IntProp")); + var helper2 = Assert.Single(PropertyHelper.GetProperties(anonymous.GetType().GetTypeInfo()).Where(prop => prop.Name == "StringProp")); + Assert.Equal(3, helper1.GetValue(anonymous)); + Assert.Equal("Five", helper2.GetValue(anonymous)); + } + + [Fact] + public void PropertyHelper_ForDerivedClass() + { + // Arrange + var derived = new DerivedClass { PropA = "propAValue", PropB = "propBValue" }; + + // Act + var helpers = PropertyHelper.GetProperties(derived.GetType().GetTypeInfo()).ToArray(); + + // Assert + Assert.NotNull(helpers); + Assert.Equal(2, helpers.Length); + + var propAHelper = Assert.Single(helpers.Where(h => h.Name == "PropA")); + var propBHelper = Assert.Single(helpers.Where(h => h.Name == "PropB")); + + Assert.Equal("propAValue", propAHelper.GetValue(derived)); + Assert.Equal("propBValue", propBHelper.GetValue(derived)); + } + + [Fact] + public void PropertyHelper_ForDerivedClass_WithNew() + { + // Arrange + var derived = new DerivedClassWithNew { PropA = "propAValue" }; + + // Act + var helpers = PropertyHelper.GetProperties(derived.GetType().GetTypeInfo()).ToArray(); + + // Assert + Assert.NotNull(helpers); + Assert.Equal(2, helpers.Length); + + var propAHelper = Assert.Single(helpers.Where(h => h.Name == "PropA")); + var propBHelper = Assert.Single(helpers.Where(h => h.Name == "PropB")); + + Assert.Equal("propAValue", propAHelper.GetValue(derived)); + Assert.Equal("Newed", propBHelper.GetValue(derived)); + } + + [Fact] + public void PropertyHelper_ForDerived_WithVirtual() + { + // Arrange + var derived = new DerivedClassWithOverride { PropA = "propAValue", PropB = "propBValue" }; + + // Act + var helpers = PropertyHelper.GetProperties(derived.GetType().GetTypeInfo()).ToArray(); + + // Assert + Assert.NotNull(helpers); + Assert.Equal(2, helpers.Length); + + var propAHelper = Assert.Single(helpers.Where(h => h.Name == "PropA")); + var propBHelper = Assert.Single(helpers.Where(h => h.Name == "PropB")); + + Assert.Equal("OverridenpropAValue", propAHelper.GetValue(derived)); + Assert.Equal("propBValue", propBHelper.GetValue(derived)); + } + + [Fact] + public void PropertyHelper_ForInterface_ReturnsExpectedProperties() + { + // Arrange + var expectedNames = new[] { "Count", "IsReadOnly" }; + + // Act + var helpers = PropertyHelper.GetProperties(typeof(ICollection)); + + // Assert + Assert.Collection( + helpers.OrderBy(helper => helper.Name, StringComparer.Ordinal), + helper => { Assert.Equal(expectedNames[0], helper.Name, StringComparer.Ordinal); }, + helper => { Assert.Equal(expectedNames[1], helper.Name, StringComparer.Ordinal); }); + } + + [Fact] + public void PropertyHelper_ForDerivedInterface_ReturnsAllProperties() + { + // Arrange + var expectedNames = new[] { "Count", "IsReadOnly", "Keys", "Values" }; + + // Act + var helpers = PropertyHelper.GetProperties(typeof(IDictionary)); + + // Assert + Assert.Collection( + helpers.OrderBy(helper => helper.Name, StringComparer.Ordinal), + helper => { Assert.Equal(expectedNames[0], helper.Name, StringComparer.Ordinal); }, + helper => { Assert.Equal(expectedNames[1], helper.Name, StringComparer.Ordinal); }, + helper => { Assert.Equal(expectedNames[2], helper.Name, StringComparer.Ordinal); }, + helper => { Assert.Equal(expectedNames[3], helper.Name, StringComparer.Ordinal); }); + } + + [Fact] + public void GetProperties_ExcludesIndexersAndPropertiesWithoutPublicGetters() + { + // Arrange + var type = typeof(DerivedClassWithNonReadableProperties); + + // Act + var result = PropertyHelper.GetProperties(type).ToArray(); + + // Assert + Assert.Equal(3, result.Length); + Assert.Equal("Visible", result[0].Name); + Assert.Equal("PropA", result[1].Name); + Assert.Equal("PropB", result[2].Name); + } + + [Fact] + public void GetVisibleProperties_NoHiddenProperty() + { + // Arrange + var type = typeof(string); + + // Act + var result = PropertyHelper.GetVisibleProperties(type).ToArray(); + + // Assert + var property = Assert.Single(result); + Assert.Equal("Length", property.Name); + Assert.Equal(typeof(int), property.Property.PropertyType); + } + + [Fact] + public void GetVisibleProperties_HiddenProperty() + { + // Arrange + var type = typeof(DerivedHiddenProperty); + + // Act + var result = PropertyHelper.GetVisibleProperties(type).ToArray(); + + // Assert + Assert.Equal(2, result.Length); + Assert.Equal("Id", result[0].Name); + Assert.Equal(typeof(string), result[0].Property.PropertyType); + Assert.Equal("Name", result[1].Name); + Assert.Equal(typeof(string), result[1].Property.PropertyType); + } + + [Fact] + public void GetVisibleProperties_HiddenProperty_TwoLevels() + { + // Arrange + var type = typeof(DerivedHiddenProperty2); + + // Act + var result = PropertyHelper.GetVisibleProperties(type).ToArray(); + + // Assert + Assert.Equal(2, result.Length); + Assert.Equal("Id", result[0].Name); + Assert.Equal(typeof(Guid), result[0].Property.PropertyType); + Assert.Equal("Name", result[1].Name); + Assert.Equal(typeof(string), result[1].Property.PropertyType); + } + + [Fact] + public void GetVisibleProperties_NoHiddenPropertyWithTypeInfoInput() + { + // Arrange + var type = typeof(string); + + // Act + var result = PropertyHelper.GetVisibleProperties(type.GetTypeInfo()).ToArray(); + + // Assert + var property = Assert.Single(result); + Assert.Equal("Length", property.Name); + Assert.Equal(typeof(int), property.Property.PropertyType); + } + + [Fact] + public void GetVisibleProperties_HiddenPropertyWithTypeInfoInput() + { + // Arrange + var type = typeof(DerivedHiddenProperty); + + // Act + var result = PropertyHelper.GetVisibleProperties(type.GetTypeInfo()).ToArray(); + + // Assert + Assert.Equal(2, result.Length); + Assert.Equal("Id", result[0].Name); + Assert.Equal(typeof(string), result[0].Property.PropertyType); + Assert.Equal("Name", result[1].Name); + Assert.Equal(typeof(string), result[1].Property.PropertyType); + } + + [Fact] + public void GetVisibleProperties_HiddenProperty_TwoLevelsWithTypeInfoInput() + { + // Arrange + var type = typeof(DerivedHiddenProperty2); + + // Act + var result = PropertyHelper.GetVisibleProperties(type.GetTypeInfo()).ToArray(); + + // Assert + Assert.Equal(2, result.Length); + Assert.Equal("Id", result[0].Name); + Assert.Equal(typeof(Guid), result[0].Property.PropertyType); + Assert.Equal("Name", result[1].Name); + Assert.Equal(typeof(string), result[1].Property.PropertyType); + } + + [Fact] + public void MakeFastPropertySetter_SetsPropertyValues_ForPublicAndNobPublicProperties() + { + // Arrange + var instance = new BaseClass(); + var typeInfo = instance.GetType().GetTypeInfo(); + var publicProperty = typeInfo.GetDeclaredProperty("PropA"); + var protectedProperty = typeInfo.GetDeclaredProperty("PropProtected"); + var publicPropertySetter = PropertyHelper.MakeFastPropertySetter(publicProperty); + var protectedPropertySetter = PropertyHelper.MakeFastPropertySetter(protectedProperty); + + // Act + publicPropertySetter(instance, "TestPublic"); + protectedPropertySetter(instance, "TestProtected"); + + // Assert + Assert.Equal("TestPublic", instance.PropA); + Assert.Equal("TestProtected", instance.GetPropProtected()); + } + + [Fact] + public void MakeFastPropertySetter_SetsPropertyValues_ForOverridenProperties() + { + // Arrange + var instance = new DerivedClassWithOverride(); + var typeInfo = instance.GetType().GetTypeInfo(); + var property = typeInfo.GetDeclaredProperty("PropA"); + var propertySetter = PropertyHelper.MakeFastPropertySetter(property); + + // Act + propertySetter(instance, "Test value"); + + // Assert + Assert.Equal("OverridenTest value", instance.PropA); + } + + [Fact] + public void MakeFastPropertySetter_SetsPropertyValues_ForNewedProperties() + { + // Arrange + var instance = new DerivedClassWithNew(); + var typeInfo = instance.GetType().GetTypeInfo(); + var property = typeInfo.GetDeclaredProperty("PropB"); + var propertySetter = PropertyHelper.MakeFastPropertySetter(property); + + // Act + propertySetter(instance, "Test value"); + + // Assert + Assert.Equal("NewedTest value", instance.PropB); + } + + [Fact] + public void MakeFastPropertyGetter_ReferenceType_ForNullObject_Throws() + { + // Arrange + var property = PropertyHelper + .GetProperties(typeof(BaseClass)) + .Single(p => p.Name == nameof(BaseClass.PropA)); + + var accessor = PropertyHelper.MakeFastPropertyGetter(property.Property); + + // Act & Assert + Assert.Throws(() => accessor(null)); + } + + [Fact] + public void MakeFastPropertyGetter_ValueType_ForNullObject_Throws() + { + // Arrange + var property = PropertyHelper + .GetProperties(typeof(MyProperties)) + .Single(p => p.Name == nameof(MyProperties.StringProp)); + + var accessor = PropertyHelper.MakeFastPropertyGetter(property.Property); + + // Act & Assert + Assert.Throws(() => accessor(null)); + } + + [Fact] + public void MakeNullSafeFastPropertyGetter_ReferenceType_Success() + { + // Arrange + var property = PropertyHelper + .GetProperties(typeof(BaseClass)) + .Single(p => p.Name == nameof(BaseClass.PropA)); + + var accessor = PropertyHelper.MakeNullSafeFastPropertyGetter(property.Property); + + // Act + var value = accessor(new BaseClass() { PropA = "Hi" }); + + // Assert + Assert.Equal("Hi", value); + } + + [Fact] + public void MakeNullSafeFastPropertyGetter_ValueType_Success() + { + // Arrange + var property = PropertyHelper + .GetProperties(typeof(MyProperties)) + .Single(p => p.Name == nameof(MyProperties.StringProp)); + + var accessor = PropertyHelper.MakeNullSafeFastPropertyGetter(property.Property); + + // Act + var value = accessor(new MyProperties() { StringProp = "Hi" }); + + // Assert + Assert.Equal("Hi", value); + } + + [Fact] + public void MakeNullSafeFastPropertyGetter_ReferenceType_ForNullObject_ReturnsNull() + { + // Arrange + var property = PropertyHelper + .GetProperties(typeof(BaseClass)) + .Single(p => p.Name == nameof(BaseClass.PropA)); + + var accessor = PropertyHelper.MakeNullSafeFastPropertyGetter(property.Property); + + // Act + var value = accessor(null); + + // Assert + Assert.Null(value); + } + + [Fact] + public void MakeNullSafeFastPropertyGetter_ValueType_ForNullObject_ReturnsNull() + { + // Arrange + var property = PropertyHelper + .GetProperties(typeof(MyProperties)) + .Single(p => p.Name == nameof(MyProperties.StringProp)); + + var accessor = PropertyHelper.MakeNullSafeFastPropertyGetter(property.Property); + + // Act + var value = accessor(null); + + // Assert + Assert.Null(value); + } + + public static TheoryData> IgnoreCaseTestData + { + get + { + return new TheoryData> + { + { + new + { + selected = true, + SeLeCtEd = false + }, + new KeyValuePair("selected", false) + }, + { + new + { + SeLeCtEd = false, + selected = true + }, + new KeyValuePair("SeLeCtEd", true) + }, + { + new + { + SelECTeD = false, + SeLECTED = true + }, + new KeyValuePair("SelECTeD", true) + } + }; + } + } + + [Theory] + [MemberData(nameof(IgnoreCaseTestData))] + public void ObjectToDictionary_IgnoresPropertyCase(object testObject, + KeyValuePair expectedEntry) + { + // Act + var result = PropertyHelper.ObjectToDictionary(testObject); + + // Assert + var entry = Assert.Single(result); + Assert.Equal(expectedEntry, entry); + } + + [Fact] + public void ObjectToDictionary_WithNullObject_ReturnsEmptyDictionary() + { + // Arrange + object value = null; + + // Act + var dictValues = PropertyHelper.ObjectToDictionary(value); + + // Assert + Assert.NotNull(dictValues); + Assert.Equal(0, dictValues.Count); + } + + [Fact] + public void ObjectToDictionary_WithPlainObjectType_ReturnsEmptyDictionary() + { + // Arrange + var value = new object(); + + // Act + var dictValues = PropertyHelper.ObjectToDictionary(value); + + // Assert + Assert.NotNull(dictValues); + Assert.Equal(0, dictValues.Count); + } + + [Fact] + public void ObjectToDictionary_WithPrimitiveType_LooksUpPublicProperties() + { + // Arrange + var value = "test"; + + // Act + var dictValues = PropertyHelper.ObjectToDictionary(value); + + // Assert + Assert.NotNull(dictValues); + Assert.Equal(1, dictValues.Count); + Assert.Equal(4, dictValues["Length"]); + } + + [Fact] + public void ObjectToDictionary_WithAnonymousType_LooksUpProperties() + { + // Arrange + var value = new { test = "value", other = 1 }; + + // Act + var dictValues = PropertyHelper.ObjectToDictionary(value); + + // Assert + Assert.NotNull(dictValues); + Assert.Equal(2, dictValues.Count); + Assert.Equal("value", dictValues["test"]); + Assert.Equal(1, dictValues["other"]); + } + + [Fact] + public void ObjectToDictionary_ReturnsCaseInsensitiveDictionary() + { + // Arrange + var value = new { TEST = "value", oThEr = 1 }; + + // Act + var dictValues = PropertyHelper.ObjectToDictionary(value); + + // Assert + Assert.NotNull(dictValues); + Assert.Equal(2, dictValues.Count); + Assert.Equal("value", dictValues["test"]); + Assert.Equal(1, dictValues["other"]); + } + + [Fact] + public void ObjectToDictionary_ReturnsInheritedProperties() + { + // Arrange + var value = new ThreeDPoint() { X = 5, Y = 10, Z = 17 }; + + // Act + var dictValues = PropertyHelper.ObjectToDictionary(value); + + // Assert + Assert.NotNull(dictValues); + Assert.Equal(3, dictValues.Count); + Assert.Equal(5, dictValues["X"]); + Assert.Equal(10, dictValues["Y"]); + Assert.Equal(17, dictValues["Z"]); + } + + private class Point + { + public int X { get; set; } + public int Y { get; set; } + } + + private class ThreeDPoint : Point + { + public int Z { get; set; } + } + + private class Static + { + public static int Prop2 { get; set; } + public int Prop5 { get; set; } + } + + private struct MyProperties + { + public int IntProp { get; set; } + public string StringProp { get; set; } + } + + private class SetOnly + { + public int Prop2 { set { } } + public int Prop6 { get; set; } + } + + private class PrivateProperties + { + public int Prop1 { get; set; } + protected int Prop2 { get; set; } + private int Prop3 { get; set; } + } + + public class BaseClass + { + public string PropA { get; set; } + + protected string PropProtected { get; set; } + + public string GetPropProtected() + { + return PropProtected; + } + } + + public class DerivedClass : BaseClass + { + public string PropB { get; set; } + } + + public class BaseClassWithVirtual + { + public virtual string PropA { get; set; } + public string PropB { get; set; } + } + + public class DerivedClassWithNew : BaseClassWithVirtual + { + private string _value = "Newed"; + + public new string PropB + { + get { return _value; } + set { _value = "Newed" + value; } + } + } + + public class DerivedClassWithOverride : BaseClassWithVirtual + { + private string _value = "Overriden"; + + public override string PropA + { + get { return _value; } + set { _value = "Overriden" + value; } + } + } + + private class DerivedClassWithNonReadableProperties : BaseClassWithVirtual + { + public string this[int index] + { + get { return string.Empty; } + set { } + } + + public int Visible { get; set; } + + private string NotVisible { get; set; } + + public string NotVisible2 { private get; set; } + + public string NotVisible3 + { + set { } + } + + public static string NotVisible4 { get; set; } + } + + private struct MyStruct + { + public string Foo { get; set; } + } + + private class BaseHiddenProperty + { + public int Id { get; set; } + } + + private class DerivedHiddenProperty : BaseHiddenProperty + { + public new string Id { get; set; } + + public string Name { get; set; } + } + + private class DerivedHiddenProperty2 : DerivedHiddenProperty + { + public new Guid Id { get; set; } + + public new string Name { get; private set; } + } + } +} diff --git a/src/Shared/test/Shared.Tests/Readme.txt b/src/Shared/test/Shared.Tests/Readme.txt new file mode 100644 index 0000000000..b818bd8148 --- /dev/null +++ b/src/Shared/test/Shared.Tests/Readme.txt @@ -0,0 +1,4 @@ +NOTE: +1. The tests for 'ExceptionDetailProvider' and 'StackTraceHelper' in project 'Microsoft.Extensions.StackTrace.Sources' are located in Diagnostics + repo. This is because they refer to some packages from FileSystem repo which causes a circular reference and breaks the + build. \ No newline at end of file diff --git a/src/Shared/test/Shared.Tests/SecurityHelperTests.cs b/src/Shared/test/Shared.Tests/SecurityHelperTests.cs new file mode 100644 index 0000000000..8e7515ad36 --- /dev/null +++ b/src/Shared/test/Shared.Tests/SecurityHelperTests.cs @@ -0,0 +1,93 @@ +// 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.Linq; +using System.Security.Claims; +using System.Security.Principal; +using Xunit; + +namespace Microsoft.Extensions.Internal +{ + public class SecurityHelperTests + { + [Fact] + public void AddingToAnonymousIdentityDoesNotKeepAnonymousIdentity() + { + var user = SecurityHelper.MergeUserPrincipal(new ClaimsPrincipal(), new GenericPrincipal(new GenericIdentity("Test1", "Alpha"), new string[0])); + + Assert.NotNull(user); + Assert.Equal("Alpha", user.Identity.AuthenticationType); + Assert.Equal("Test1", user.Identity.Name); + Assert.IsAssignableFrom(user); + Assert.IsAssignableFrom(user.Identity); + Assert.Single(user.Identities); + } + + [Fact] + public void AddingExistingIdentityChangesDefaultButPreservesPrior() + { + ClaimsPrincipal user = new GenericPrincipal(new GenericIdentity("Test1", "Alpha"), null); + + Assert.Equal("Alpha", user.Identity.AuthenticationType); + Assert.Equal("Test1", user.Identity.Name); + + user = SecurityHelper.MergeUserPrincipal(user, new GenericPrincipal(new GenericIdentity("Test2", "Beta"), new string[0])); + + Assert.Equal("Beta", user.Identity.AuthenticationType); + Assert.Equal("Test2", user.Identity.Name); + + user = SecurityHelper.MergeUserPrincipal(user, new GenericPrincipal(new GenericIdentity("Test3", "Gamma"), new string[0])); + + Assert.Equal("Gamma", user.Identity.AuthenticationType); + Assert.Equal("Test3", user.Identity.Name); + + Assert.Equal(3, user.Identities.Count()); + Assert.Equal("Test3", user.Identities.Skip(0).First().Name); + Assert.Equal("Test2", user.Identities.Skip(1).First().Name); + Assert.Equal("Test1", user.Identities.Skip(2).First().Name); + } + + [Fact] + public void AddingPreservesNewIdentitiesAndDropsEmpty() + { + var existingPrincipal = new ClaimsPrincipal(new ClaimsIdentity()); + var identityNoAuthTypeWithClaim = new ClaimsIdentity(); + identityNoAuthTypeWithClaim.AddClaim(new Claim("identityNoAuthTypeWithClaim", "yes")); + existingPrincipal.AddIdentity(identityNoAuthTypeWithClaim); + var identityEmptyWithAuthType = new ClaimsIdentity("empty"); + existingPrincipal.AddIdentity(identityEmptyWithAuthType); + + Assert.False(existingPrincipal.Identity.IsAuthenticated); + + var newPrincipal = new ClaimsPrincipal(); + var newEmptyIdentity = new ClaimsIdentity(); + var identityTwo = new ClaimsIdentity("yep"); + newPrincipal.AddIdentity(newEmptyIdentity); + newPrincipal.AddIdentity(identityTwo); + + var user = SecurityHelper.MergeUserPrincipal(existingPrincipal, newPrincipal); + + // Preserve newPrincipal order + Assert.False(user.Identity.IsAuthenticated); + Assert.Null(user.Identity.Name); + + Assert.Equal(4, user.Identities.Count()); + Assert.Equal(newEmptyIdentity, user.Identities.Skip(0).First()); + Assert.Equal(identityTwo, user.Identities.Skip(1).First()); + Assert.Equal(identityNoAuthTypeWithClaim, user.Identities.Skip(2).First()); + Assert.Equal(identityEmptyWithAuthType, user.Identities.Skip(3).First()); + + // This merge should drop newEmptyIdentity since its empty + user = SecurityHelper.MergeUserPrincipal(user, new GenericPrincipal(new GenericIdentity("Test3", "Gamma"), new string[0])); + + Assert.Equal("Gamma", user.Identity.AuthenticationType); + Assert.Equal("Test3", user.Identity.Name); + + Assert.Equal(4, user.Identities.Count()); + Assert.Equal("Test3", user.Identities.Skip(0).First().Name); + Assert.Equal(identityTwo, user.Identities.Skip(1).First()); + Assert.Equal(identityNoAuthTypeWithClaim, user.Identities.Skip(2).First()); + Assert.Equal(identityEmptyWithAuthType, user.Identities.Skip(3).First()); + } + } +} diff --git a/src/Shared/test/Shared.Tests/StackTraceHelperTest.cs b/src/Shared/test/Shared.Tests/StackTraceHelperTest.cs new file mode 100644 index 0000000000..657a310b6e --- /dev/null +++ b/src/Shared/test/Shared.Tests/StackTraceHelperTest.cs @@ -0,0 +1,345 @@ +// 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.Collections.Generic; +using System.Linq; +using System.Linq.Expressions; +using System.Runtime.CompilerServices; +using System.Threading.Tasks; +using Microsoft.Extensions.StackTrace.Sources; +using ThrowingLibrary; +using Xunit; + +namespace Microsoft.Extensions.Internal +{ + public class StackTraceHelperTest + { + [Fact] + public void StackTraceHelper_IncludesLineNumbersForFiles() + { + // Arrange + Exception exception = null; + try + { + // Throwing an exception in the current assembly always seems to populate the full stack + // trace regardless of symbol type. Crossing assembly boundaries ensures PortablePdbReader gets used + // on desktop. + Thrower.Throw(); + } + catch (Exception ex) + { + exception = ex; + } + + // Act + var stackFrames = StackTraceHelper.GetFrames(exception); + + // Assert + Assert.Collection(stackFrames, + frame => + { + Assert.Contains("Thrower.cs", frame.FilePath); + Assert.Equal(17, frame.LineNumber); + }, + frame => + { + Assert.Contains("StackTraceHelperTest.cs", frame.FilePath); + }); + } + + [Fact] + public void StackTraceHelper_PrettyPrintsStackTraceForGenericMethods() + { + // Arrange + var exception = Record.Exception(() => GenericMethod(null)); + + // Act + var stackFrames = StackTraceHelper.GetFrames(exception); + + // Assert + var methods = stackFrames.Select(frame => frame.MethodDisplayInfo.ToString()).ToArray(); + Assert.Equal("Microsoft.Extensions.Internal.StackTraceHelperTest.GenericMethod(T val)", methods[0]); + } + + [Fact] + public void StackTraceHelper_PrettyPrintsStackTraceForMethodsWithOutParameters() + { + // Arrange + var exception = Record.Exception(() => MethodWithOutParameter(out var value)); + + // Act + var stackFrames = StackTraceHelper.GetFrames(exception); + + // Assert + var methods = stackFrames.Select(frame => frame.MethodDisplayInfo.ToString()).ToArray(); + Assert.Equal("Microsoft.Extensions.Internal.StackTraceHelperTest.MethodWithOutParameter(out int value)", methods[0]); + } + + [Fact] + public void StackTraceHelper_PrettyPrintsStackTraceForMethodsWithGenericOutParameters() + { + // Arrange + var exception = Record.Exception(() => MethodWithGenericOutParameter("Test", out int value)); + + // Act + var stackFrames = StackTraceHelper.GetFrames(exception); + + // Assert + var methods = stackFrames.Select(frame => frame.MethodDisplayInfo.ToString()).ToArray(); + Assert.Equal("Microsoft.Extensions.Internal.StackTraceHelperTest.MethodWithGenericOutParameter(string a, out TVal value)", methods[0]); + } + + [Fact] + public void StackTraceHelper_PrettyPrintsStackTraceForMethodsWithRefParameters() + { + // Arrange + var value = 0; + var exception = Record.Exception(() => MethodWithRefParameter(ref value)); + + // Act + var stackFrames = StackTraceHelper.GetFrames(exception); + + // Assert + var methods = stackFrames.Select(frame => frame.MethodDisplayInfo.ToString()).ToArray(); + Assert.Equal("Microsoft.Extensions.Internal.StackTraceHelperTest.MethodWithRefParameter(ref int value)", methods[0]); + } + + [Fact] + public void StackTraceHelper_PrettyPrintsStackTraceForMethodsWithGenericRefParameters() + { + // Arrange + var value = 0; + var exception = Record.Exception(() => MethodWithGenericRefParameter(ref value)); + + // Act + var stackFrames = StackTraceHelper.GetFrames(exception); + + // Assert + var methods = stackFrames.Select(frame => frame.MethodDisplayInfo.ToString()).ToArray(); + Assert.Equal("Microsoft.Extensions.Internal.StackTraceHelperTest.MethodWithGenericRefParameter(ref TVal value)", methods[0]); + } + + [Fact] + public void StackTraceHelper_PrettyPrintsStackTraceForMethodsWithNullableParameters() + { + // Arrange + var value = 0; + var exception = Record.Exception(() => MethodWithNullableParameter(value)); + + // Act + var stackFrames = StackTraceHelper.GetFrames(exception); + + // Assert + var methods = stackFrames.Select(frame => frame.MethodDisplayInfo.ToString()).ToArray(); + Assert.Equal("Microsoft.Extensions.Internal.StackTraceHelperTest.MethodWithNullableParameter(Nullable value)", methods[0]); + } + + [Fact] + public void StackTraceHelper_PrettyPrintsStackTraceForMethodsOnGenericTypes() + { + // Arrange + var exception = Record.Exception(() => new GenericClass().Throw(0)); + + // Act + var stackFrames = StackTraceHelper.GetFrames(exception); + + // Assert + var methods = stackFrames.Select(frame => frame.MethodDisplayInfo.ToString()).ToArray(); + Assert.Equal("Microsoft.Extensions.Internal.StackTraceHelperTest+GenericClass.Throw(T parameter)", methods[0]); + } + + [Fact] + public void StackTraceHelper_ProducesReadableOutput() + { + // Arrange + var expectedCallStack = new List() + { + "Microsoft.Extensions.Internal.StackTraceHelperTest.Iterator()+MoveNext()", + "string.Join(string separator, IEnumerable values)", + "Microsoft.Extensions.Internal.StackTraceHelperTest+GenericClass.GenericMethod(ref V value)", + "Microsoft.Extensions.Internal.StackTraceHelperTest.MethodAsync(int value)", + "Microsoft.Extensions.Internal.StackTraceHelperTest.MethodAsync(TValue value)", + "Microsoft.Extensions.Internal.StackTraceHelperTest.Method(string value)", + "Microsoft.Extensions.Internal.StackTraceHelperTest.StackTraceHelper_ProducesReadableOutput()", + }; + + Exception exception = null; + try + { + Method("test"); + } + catch (Exception ex) + { + exception = ex; + } + + // Act + var stackFrames = StackTraceHelper.GetFrames(exception); + var methodNames = stackFrames.Select(stackFrame => stackFrame.MethodDisplayInfo.ToString()).ToArray(); + + // Assert + Assert.Equal(expectedCallStack, methodNames); + } + + [Fact] + public void StackTraceHelper_DoesNotIncludeInstanceMethodsOnTypesWithStackTraceHiddenAttribute() + { + // Arrange + var exception = Record.Exception(() => InvokeMethodOnTypeWithStackTraceHiddenAttribute()); + + // Act + var stackFrames = StackTraceHelper.GetFrames(exception); + + // Assert + var methods = stackFrames.Select(frame => frame.MethodDisplayInfo.ToString()).ToArray(); + Assert.Equal("Microsoft.Extensions.Internal.StackTraceHelperTest.ThrowCore()", methods[0]); + Assert.Equal("Microsoft.Extensions.Internal.StackTraceHelperTest.InvokeMethodOnTypeWithStackTraceHiddenAttribute()", methods[1]); + } + + [Fact] + public void StackTraceHelper_DoesNotIncludeStaticMethodsOnTypesWithStackTraceHiddenAttribute() + { + // Arrange + var exception = Record.Exception(() => InvokeStaticMethodOnTypeWithStackTraceHiddenAttribute()); + + // Act + var stackFrames = StackTraceHelper.GetFrames(exception); + + // Assert + var methods = stackFrames.Select(frame => frame.MethodDisplayInfo.ToString()).ToArray(); + Assert.Equal("Microsoft.Extensions.Internal.StackTraceHelperTest.ThrowCore()", methods[0]); + Assert.Equal("Microsoft.Extensions.Internal.StackTraceHelperTest.InvokeStaticMethodOnTypeWithStackTraceHiddenAttribute()", methods[1]); + } + + [Fact] + public void StackTraceHelper_DoesNotIncludeMethodsWithStackTraceHiddenAttribute() + { + // Arrange + var exception = Record.Exception(() => new TypeWithMethodWithStackTraceHiddenAttribute().Throw()); + + // Act + var stackFrames = StackTraceHelper.GetFrames(exception); + + // Assert + var methods = stackFrames.Select(frame => frame.MethodDisplayInfo.ToString()).ToArray(); + Assert.Equal("Microsoft.Extensions.Internal.StackTraceHelperTest.ThrowCore()", methods[0]); + Assert.Equal("Microsoft.Extensions.Internal.StackTraceHelperTest+TypeWithMethodWithStackTraceHiddenAttribute.Throw()", methods[1]); + } + + [Fact] + public void GetFrames_DoesNotFailForDynamicallyGeneratedAssemblies() + { + // Arrange + var action = (Action)Expression.Lambda( + Expression.Throw( + Expression.New(typeof(Exception)))).Compile(); + var exception = Record.Exception(action); + + // Act + var frames = StackTraceHelper.GetFrames(exception).ToArray(); + + // Assert + var frame = frames[0]; + Assert.Null(frame.FilePath); + Assert.Equal($"lambda_method(Closure )", frame.MethodDisplayInfo.ToString()); + } + + [MethodImpl(MethodImplOptions.NoOptimization | MethodImplOptions.NoInlining)] + async Task MethodAsync(int value) + { + await Task.Delay(0); + return GenericClass.GenericMethod(ref value); + } + + [MethodImpl(MethodImplOptions.NoOptimization | MethodImplOptions.NoInlining)] + async Task MethodAsync(TValue value) + { + return await MethodAsync(1); + } + + [MethodImpl(MethodImplOptions.NoOptimization | MethodImplOptions.NoInlining)] + string Method(string value) + { + return MethodAsync(value).GetAwaiter().GetResult(); + } + + [MethodImpl(MethodImplOptions.NoOptimization | MethodImplOptions.NoInlining)] + static IEnumerable Iterator() + { + yield return "Success"; + throw new Exception(); + } + + [MethodImpl(MethodImplOptions.NoOptimization | MethodImplOptions.NoInlining)] + void MethodWithOutParameter(out int value) => throw new Exception(); + + [MethodImpl(MethodImplOptions.NoOptimization | MethodImplOptions.NoInlining)] + void MethodWithGenericOutParameter(string a, out TVal value) => throw new Exception(); + + [MethodImpl(MethodImplOptions.NoOptimization | MethodImplOptions.NoInlining)] + void MethodWithRefParameter(ref int value) => throw new Exception(); + + [MethodImpl(MethodImplOptions.NoOptimization | MethodImplOptions.NoInlining)] + void MethodWithGenericRefParameter(ref TVal value) => throw new Exception(); + + [MethodImpl(MethodImplOptions.NoOptimization | MethodImplOptions.NoInlining)] + void MethodWithNullableParameter(int? value) => throw new Exception(); + + [MethodImpl(MethodImplOptions.NoOptimization | MethodImplOptions.NoInlining)] + void InvokeMethodOnTypeWithStackTraceHiddenAttribute() => new TypeWithStackTraceHiddenAttribute().Throw(); + + [MethodImpl(MethodImplOptions.NoOptimization | MethodImplOptions.NoInlining)] + void InvokeStaticMethodOnTypeWithStackTraceHiddenAttribute() => TypeWithStackTraceHiddenAttribute.ThrowStatic(); + + class GenericClass + { + [MethodImpl(MethodImplOptions.NoOptimization | MethodImplOptions.NoInlining)] + public static string GenericMethod(ref V value) + { + var returnVal = ""; + for (var i = 0; i < 10; i++) + { + returnVal += string.Join(", ", Iterator()); + } + return returnVal; + } + + [MethodImpl(MethodImplOptions.NoOptimization | MethodImplOptions.NoInlining)] + public void Throw(T parameter) => throw new Exception(); + } + + [MethodImpl(MethodImplOptions.NoOptimization | MethodImplOptions.NoInlining)] + private void GenericMethod(T val) where T : class => throw new Exception(); + + private class StackTraceHiddenAttribute : Attribute + { + } + + [StackTraceHidden] + private class TypeWithStackTraceHiddenAttribute + { + [MethodImpl(MethodImplOptions.NoOptimization | MethodImplOptions.NoInlining)] + public void Throw() => ThrowCore(); + + [MethodImpl(MethodImplOptions.NoOptimization | MethodImplOptions.NoInlining)] + public static void ThrowStatic() => ThrowCore(); + } + + private class TypeWithMethodWithStackTraceHiddenAttribute + { + [MethodImpl(MethodImplOptions.NoOptimization | MethodImplOptions.NoInlining)] + [StackTraceHidden] + public void MethodWithStackTraceHiddenAttribute() + { + ThrowCore(); + } + + [MethodImpl(MethodImplOptions.NoOptimization | MethodImplOptions.NoInlining)] + public void Throw() => MethodWithStackTraceHiddenAttribute(); + } + + [MethodImpl(MethodImplOptions.NoOptimization | MethodImplOptions.NoInlining)] + private static void ThrowCore() => throw new Exception(); + } +} diff --git a/src/Shared/test/Shared.Tests/TypeNameHelperTest.cs b/src/Shared/test/Shared.Tests/TypeNameHelperTest.cs new file mode 100644 index 0000000000..b7f4285bdc --- /dev/null +++ b/src/Shared/test/Shared.Tests/TypeNameHelperTest.cs @@ -0,0 +1,263 @@ +// 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.Collections.Generic; +using Xunit; + +namespace Microsoft.Extensions.Internal +{ + public class TypeNameHelperTest + { + [Theory] + // Predefined Types + [InlineData(typeof(int), "int")] + [InlineData(typeof(List), "System.Collections.Generic.List")] + [InlineData(typeof(Dictionary), "System.Collections.Generic.Dictionary")] + [InlineData(typeof(Dictionary>), "System.Collections.Generic.Dictionary>")] + [InlineData(typeof(List>), "System.Collections.Generic.List>")] + // Classes inside NonGeneric class + [InlineData(typeof(A), + "Microsoft.Extensions.Internal.TypeNameHelperTest+A")] + [InlineData(typeof(B), + "Microsoft.Extensions.Internal.TypeNameHelperTest+B")] + [InlineData(typeof(C), + "Microsoft.Extensions.Internal.TypeNameHelperTest+C")] + [InlineData(typeof(B>), + "Microsoft.Extensions.Internal.TypeNameHelperTest+B>")] + [InlineData(typeof(C>), + "Microsoft.Extensions.Internal.TypeNameHelperTest+C>")] + // Classes inside Generic class + [InlineData(typeof(Outer.D), + "Microsoft.Extensions.Internal.TypeNameHelperTest+Outer+D")] + [InlineData(typeof(Outer.E), + "Microsoft.Extensions.Internal.TypeNameHelperTest+Outer+E")] + [InlineData(typeof(Outer.F), + "Microsoft.Extensions.Internal.TypeNameHelperTest+Outer+F")] + [InlineData(typeof(Level1.Level2.Level3), + "Microsoft.Extensions.Internal.TypeNameHelperTest+Level1+Level2+Level3")] + [InlineData(typeof(Outer.E.E>), + "Microsoft.Extensions.Internal.TypeNameHelperTest+Outer+E+E>")] + [InlineData(typeof(Outer.F.E>), + "Microsoft.Extensions.Internal.TypeNameHelperTest+Outer+F+E>")] + [InlineData(typeof(OuterGeneric.InnerNonGeneric.InnerGeneric.InnerGenericLeafNode), + "Microsoft.Extensions.Internal.TypeNameHelperTest+OuterGeneric+InnerNonGeneric+InnerGeneric+InnerGenericLeafNode")] + public void Can_pretty_print_CLR_full_name(Type type, string expected) + { + Assert.Equal(expected, TypeNameHelper.GetTypeDisplayName(type)); + } + + [Fact] + public void DoesNotPrintNamespace_ForGenericTypes_IfNullOrEmpty() + { + // Arrange + var type = typeof(ClassInGlobalNamespace); + + // Act & Assert + Assert.Equal("ClassInGlobalNamespace", TypeNameHelper.GetTypeDisplayName(type)); + } + + [Theory] + // Predefined Types + [InlineData(typeof(int), "int")] + [InlineData(typeof(List), "List")] + [InlineData(typeof(Dictionary), "Dictionary")] + [InlineData(typeof(Dictionary>), "Dictionary>")] + [InlineData(typeof(List>), "List>")] + // Classes inside NonGeneric class + [InlineData(typeof(A), "A")] + [InlineData(typeof(B), "B")] + [InlineData(typeof(C), "C")] + [InlineData(typeof(C>), "C>")] + [InlineData(typeof(B>), "B>")] + // Classes inside Generic class + [InlineData(typeof(Outer.D), "D")] + [InlineData(typeof(Outer.E), "E")] + [InlineData(typeof(Outer.F), "F")] + [InlineData(typeof(Outer.F.E>), "F>")] + [InlineData(typeof(Outer.E.E>), "E>")] + [InlineData(typeof(OuterGeneric.InnerNonGeneric.InnerGeneric.InnerGenericLeafNode), "InnerGenericLeafNode")] + public void Can_pretty_print_CLR_name(Type type, string expected) + { + Assert.Equal(expected, TypeNameHelper.GetTypeDisplayName(type, false)); + } + + [Theory] + [InlineData(typeof(void), "void")] + [InlineData(typeof(bool), "bool")] + [InlineData(typeof(byte), "byte")] + [InlineData(typeof(char), "char")] + [InlineData(typeof(decimal), "decimal")] + [InlineData(typeof(double), "double")] + [InlineData(typeof(float), "float")] + [InlineData(typeof(int), "int")] + [InlineData(typeof(long), "long")] + [InlineData(typeof(object), "object")] + [InlineData(typeof(sbyte), "sbyte")] + [InlineData(typeof(short), "short")] + [InlineData(typeof(string), "string")] + [InlineData(typeof(uint), "uint")] + [InlineData(typeof(ulong), "ulong")] + [InlineData(typeof(ushort), "ushort")] + public void Returns_common_name_for_built_in_types(Type type, string expected) + { + Assert.Equal(expected, TypeNameHelper.GetTypeDisplayName(type)); + } + + [Theory] + [InlineData(typeof(int[]), true, "int[]")] + [InlineData(typeof(string[][]), true, "string[][]")] + [InlineData(typeof(int[,]), true, "int[,]")] + [InlineData(typeof(bool[,,,]), true, "bool[,,,]")] + [InlineData(typeof(A[,][,,]), true, "Microsoft.Extensions.Internal.TypeNameHelperTest+A[,][,,]")] + [InlineData(typeof(List), true, "System.Collections.Generic.List")] + [InlineData(typeof(List[,][,,]), false, "List[,][,,]")] + public void Can_pretty_print_array_name(Type type, bool fullName, string expected) + { + Assert.Equal(expected, TypeNameHelper.GetTypeDisplayName(type, fullName)); + } + + public static TheoryData GetOpenGenericsTestData() + { + var openDictionaryType = typeof(Dictionary<,>); + var genArgsDictionary = openDictionaryType.GetGenericArguments(); + genArgsDictionary[0] = typeof(B<>); + var closedDictionaryType = openDictionaryType.MakeGenericType(genArgsDictionary); + + var openLevelType = typeof(Level1<>.Level2<>.Level3<>); + var genArgsLevel = openLevelType.GetGenericArguments(); + genArgsLevel[1] = typeof(string); + var closedLevelType = openLevelType.MakeGenericType(genArgsLevel); + + var openInnerType = typeof(OuterGeneric<>.InnerNonGeneric.InnerGeneric<,>.InnerGenericLeafNode<>); + var genArgsInnerType = openInnerType.GetGenericArguments(); + genArgsInnerType[3] = typeof(bool); + var closedInnerType = openInnerType.MakeGenericType(genArgsInnerType); + + return new TheoryData + { + { typeof(List<>), false, "List<>" }, + { typeof(Dictionary<,>), false , "Dictionary<,>" }, + { typeof(List<>), true , "System.Collections.Generic.List<>" }, + { typeof(Dictionary<,>), true , "System.Collections.Generic.Dictionary<,>" }, + { typeof(Level1<>.Level2<>.Level3<>), true, "Microsoft.Extensions.Internal.TypeNameHelperTest+Level1<>+Level2<>+Level3<>" }, + { + typeof(PartiallyClosedGeneric<>).BaseType, + true, + "Microsoft.Extensions.Internal.TypeNameHelperTest+C<, int>" + }, + { + typeof(OuterGeneric<>.InnerNonGeneric.InnerGeneric<,>.InnerGenericLeafNode<>), + true, + "Microsoft.Extensions.Internal.TypeNameHelperTest+OuterGeneric<>+InnerNonGeneric+InnerGeneric<,>+InnerGenericLeafNode<>" + }, + { + closedDictionaryType, + true, + "System.Collections.Generic.Dictionary,>" + }, + { + closedLevelType, + true, + "Microsoft.Extensions.Internal.TypeNameHelperTest+Level1<>+Level2+Level3<>" + }, + { + closedInnerType, + true, + "Microsoft.Extensions.Internal.TypeNameHelperTest+OuterGeneric<>+InnerNonGeneric+InnerGeneric<,>+InnerGenericLeafNode" + } + }; + } + + [Theory] + [MemberData(nameof(GetOpenGenericsTestData))] + public void Can_pretty_print_open_generics(Type type, bool fullName, string expected) + { + Assert.Equal(expected, TypeNameHelper.GetTypeDisplayName(type, fullName)); + } + + public static TheoryData GetTypeDisplayName_IncludesGenericParameterNamesWhenOptionIsSetData => + new TheoryData + { + { typeof(B<>),"Microsoft.Extensions.Internal.TypeNameHelperTest+B" }, + { typeof(C<,>),"Microsoft.Extensions.Internal.TypeNameHelperTest+C" }, + { typeof(PartiallyClosedGeneric<>).BaseType,"Microsoft.Extensions.Internal.TypeNameHelperTest+C" }, + { typeof(Level1<>.Level2<>),"Microsoft.Extensions.Internal.TypeNameHelperTest+Level1+Level2" }, + }; + + [Theory] + [MemberData(nameof(GetTypeDisplayName_IncludesGenericParameterNamesWhenOptionIsSetData))] + public void GetTypeDisplayName_IncludesGenericParameterNamesWhenOptionIsSet(Type type, string expected) + { + // Arrange & Act + var actual = TypeNameHelper.GetTypeDisplayName(type, fullName: true, includeGenericParameterNames: true); + + // Assert + Assert.Equal(expected, actual); + } + + public static TheoryData GetTypeDisplayName_WithoutFullName_IncludesGenericParameterNamesWhenOptionIsSetData => + new TheoryData + { + { typeof(B<>),"B" }, + { typeof(C<,>),"C" }, + { typeof(PartiallyClosedGeneric<>).BaseType,"C" }, + { typeof(Level1<>.Level2<>),"Level2" }, + }; + + [Theory] + [MemberData(nameof(GetTypeDisplayName_WithoutFullName_IncludesGenericParameterNamesWhenOptionIsSetData))] + public void GetTypeDisplayName_WithoutFullName_IncludesGenericParameterNamesWhenOptionIsSet(Type type, string expected) + { + // Arrange & Act + var actual = TypeNameHelper.GetTypeDisplayName(type, fullName: false, includeGenericParameterNames: true); + + // Assert + Assert.Equal(expected, actual); + } + + private class A { } + + private class B { } + + private class C { } + + private class PartiallyClosedGeneric : C { } + + private class Outer + { + public class D { } + + public class E { } + + public class F { } + } + + private class OuterGeneric + { + public class InnerNonGeneric + { + public class InnerGeneric + { + public class InnerGenericLeafNode { } + + public class InnerLeafNode { } + } + } + } + + private class Level1 + { + public class Level2 + { + public class Level3 + { + } + } + } + } +} + +internal class ClassInGlobalNamespace +{ +} diff --git a/src/Shared/test/Shared.Tests/ValueStopwatchTest.cs b/src/Shared/test/Shared.Tests/ValueStopwatchTest.cs new file mode 100644 index 0000000000..fffc2c6656 --- /dev/null +++ b/src/Shared/test/Shared.Tests/ValueStopwatchTest.cs @@ -0,0 +1,39 @@ +// 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.Threading.Tasks; +using Xunit; + +namespace Microsoft.Extensions.Internal.Test +{ + public class ValueStopwatchTest + { + [Fact] + public void IsActiveIsFalseForDefaultValueStopwatch() + { + Assert.False(default(ValueStopwatch).IsActive); + } + + [Fact] + public void IsActiveIsTrueWhenValueStopwatchStartedWithStartNew() + { + Assert.True(ValueStopwatch.StartNew().IsActive); + } + + [Fact] + public void GetElapsedTimeThrowsIfValueStopwatchIsDefaultValue() + { + var stopwatch = default(ValueStopwatch); + Assert.Throws(() => stopwatch.GetElapsedTime()); + } + + [Fact] + public async Task GetElapsedTimeReturnsTimeElapsedSinceStart() + { + var stopwatch = ValueStopwatch.StartNew(); + await Task.Delay(200); + Assert.True(stopwatch.GetElapsedTime().TotalMilliseconds > 0); + } + } +} diff --git a/src/Shared/test/Shared.Tests/WebEncodersTests.cs b/src/Shared/test/Shared.Tests/WebEncodersTests.cs new file mode 100644 index 0000000000..5c71403fd6 --- /dev/null +++ b/src/Shared/test/Shared.Tests/WebEncodersTests.cs @@ -0,0 +1,113 @@ +// 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.Linq; +using Xunit; + +namespace Microsoft.Extensions.Internal +{ + public class WebEncodersTests + { + [Theory] + [InlineData("", 1, 0)] + [InlineData("", 0, 1)] + [InlineData("0123456789", 9, 2)] + [InlineData("0123456789", Int32.MaxValue, 2)] + [InlineData("0123456789", 9, -1)] + public void Base64UrlDecode_BadOffsets(string input, int offset, int count) + { + // Act & assert + Assert.ThrowsAny(() => + { + var retVal = WebEncoders.Base64UrlDecode(input, offset, count); + }); + } + + [Theory] + [InlineData("x")] + [InlineData("(x)")] + public void Base64UrlDecode_MalformedInput(string input) + { + // Act & assert + Assert.Throws(() => + { + var retVal = WebEncoders.Base64UrlDecode(input); + }); + } + + [Theory] + [InlineData("", "")] + [InlineData("123456qwerty++//X+/x", "123456qwerty--__X-_x")] + [InlineData("123456qwerty++//X+/xxw==", "123456qwerty--__X-_xxw")] + [InlineData("123456qwerty++//X+/xxw0=", "123456qwerty--__X-_xxw0")] + public void Base64UrlEncode_And_Decode(string base64Input, string expectedBase64Url) + { + // Arrange + byte[] input = new byte[3].Concat(Convert.FromBase64String(base64Input)).Concat(new byte[2]).ToArray(); + + // Act & assert - 1 + string actualBase64Url = WebEncoders.Base64UrlEncode(input, 3, input.Length - 5); // also helps test offsets + Assert.Equal(expectedBase64Url, actualBase64Url); + + // Act & assert - 2 + // Verify that values round-trip + byte[] roundTripped = WebEncoders.Base64UrlDecode("xx" + actualBase64Url + "yyy", 2, actualBase64Url.Length); // also helps test offsets + string roundTrippedAsBase64 = Convert.ToBase64String(roundTripped); + Assert.Equal(roundTrippedAsBase64, base64Input); + } + + [Theory] + [InlineData("", "")] + [InlineData("123456qwerty++//X+/x", "123456qwerty--__X-_x")] + [InlineData("123456qwerty++//X+/xxw==", "123456qwerty--__X-_xxw")] + [InlineData("123456qwerty++//X+/xxw0=", "123456qwerty--__X-_xxw0")] + public void Base64UrlEncode_And_Decode_WithBufferOffsets(string base64Input, string expectedBase64Url) + { + // Arrange + var input = new byte[3].Concat(Convert.FromBase64String(base64Input)).Concat(new byte[2]).ToArray(); + var buffer = new char[30]; + var output = new char[30]; + for (var i = 0; i < buffer.Length; i++) + { + buffer[i] = '^'; + output[i] = '^'; + } + + // Act 1 + var numEncodedChars = + WebEncoders.Base64UrlEncode(input, offset: 3, output: output, outputOffset: 4, count: input.Length - 5); + + // Assert 1 + var encodedString = new string(output, startIndex: 4, length: numEncodedChars); + Assert.Equal(expectedBase64Url, encodedString); + + // Act 2 + var roundTripInput = new string(output); + var roundTripped = + WebEncoders.Base64UrlDecode(roundTripInput, offset: 4, buffer: buffer, bufferOffset: 5, count: numEncodedChars); + + // Assert 2, verify that values round-trip + var roundTrippedAsBase64 = Convert.ToBase64String(roundTripped); + Assert.Equal(roundTrippedAsBase64, base64Input); + } + + [Theory] + [InlineData(0, 1, 0)] + [InlineData(0, 0, 1)] + [InlineData(10, 9, 2)] + [InlineData(10, Int32.MaxValue, 2)] + [InlineData(10, 9, -1)] + public void Base64UrlEncode_BadOffsets(int inputLength, int offset, int count) + { + // Arrange + byte[] input = new byte[inputLength]; + + // Act & assert + Assert.ThrowsAny(() => + { + var retVal = WebEncoders.Base64UrlEncode(input, offset, count); + }); + } + } +} diff --git a/src/Shared/test/testassets/ThrowingLibrary/Thrower.cs b/src/Shared/test/testassets/ThrowingLibrary/Thrower.cs new file mode 100644 index 0000000000..babe2387c6 --- /dev/null +++ b/src/Shared/test/testassets/ThrowingLibrary/Thrower.cs @@ -0,0 +1,20 @@ +// 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 ThrowingLibrary +{ + // Throwing an exception in the current assembly always seems to populate the full stack + // trace regardless of symbol type. This type exists to simulate an exception thrown + // across assemblies which is the typical use case for StackTraceHelper. + public static class Thrower + { + [MethodImpl(MethodImplOptions.NoInlining)] + public static void Throw() + { + throw new DivideByZeroException(); + } + } +} diff --git a/src/Shared/test/testassets/ThrowingLibrary/ThrowingLibrary.csproj b/src/Shared/test/testassets/ThrowingLibrary/ThrowingLibrary.csproj new file mode 100644 index 0000000000..d77d392873 --- /dev/null +++ b/src/Shared/test/testassets/ThrowingLibrary/ThrowingLibrary.csproj @@ -0,0 +1,8 @@ + + + + netstandard2.0 + portable + + + From 5460fd093ebfc95db12656c5f389de09e0572b2e Mon Sep 17 00:00:00 2001 From: Nate McMaster Date: Tue, 30 Oct 2018 16:32:28 -0700 Subject: [PATCH 0002/1101] Fix the package id for Microsoft.AspNetCore.BenchmarkRunner.Sources \n\nCommit migrated from https://github.com/dotnet/extensions/commit/d0aa5c17017254eeba987f283f401a724e88a56f --- src/Shared/BenchmarkRunner/Directory.Build.props | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Shared/BenchmarkRunner/Directory.Build.props b/src/Shared/BenchmarkRunner/Directory.Build.props index 34cf593291..d2f65e8d3d 100644 --- a/src/Shared/BenchmarkRunner/Directory.Build.props +++ b/src/Shared/BenchmarkRunner/Directory.Build.props @@ -3,6 +3,6 @@ - Microsoft.Extensions.$(ProjectDirName).Sources + Microsoft.AspNetCore.BenchmarkRunner.Sources From 45b0b83997cf5369d1e6541952af4e35de31c694 Mon Sep 17 00:00:00 2001 From: Nate McMaster Date: Tue, 30 Oct 2018 16:39:06 -0700 Subject: [PATCH 0003/1101] Merge branch 'release/2.1' into release/2.2 \n\nCommit migrated from https://github.com/dotnet/extensions/commit/18fcffbd251abf944e4cc82a3c775882f687a1b2 --- src/ObjectPool/src/DefaultObjectPool.cs | 17 +- src/ObjectPool/test/DefaultObjectPoolTest.cs | 4 +- src/ObjectPool/test/ThreadingTest.cs | 80 ++++ .../AspNetCoreBenchmarkAttribute.cs | 66 ++- .../BenchmarkRunner/DefaultCoreConfig.cs | 4 + .../BenchmarkRunner/DefaultCoreDebugConfig.cs | 23 + .../DefaultCorePerfLabConfig.cs | 48 ++ .../DefaultCoreProfileConfig.cs | 32 ++ .../ParamsDisplayInfoColumn.cs | 26 ++ src/Shared/BenchmarkRunner/Program.cs | 32 +- .../CertificateManager.cs | 425 ++++++++++++++---- .../CertificatePurpose.cs | 3 +- .../EnsureCertificateResult.cs | 2 +- .../NonCapturingTimer/NonCapturingTimer.cs | 43 ++ src/Shared/Process/ProcessHelper.cs | 35 +- src/Shared/PropertyHelper/PropertyHelper.cs | 29 +- .../Shared.Tests/CertificateManagerTests.cs | 196 ++++---- .../test/Shared.Tests/DotNetMuxerTests.cs | 2 +- .../Shared.Tests/NonCapturingTimerTest.cs | 40 ++ .../test/Shared.Tests/PropertyHelperTest.cs | 32 ++ .../ThrowingLibrary/ThrowingLibrary.csproj | 1 + src/Testing/src/CultureReplacer.cs | 79 ++++ src/Testing/src/ExceptionAssertions.cs | 271 +++++++++++ src/Testing/src/HttpClientSlim.cs | 158 +++++++ .../src/Microsoft.AspNetCore.Testing.csproj | 33 ++ src/Testing/src/Properties/AssemblyInfo.cs | 6 + src/Testing/src/ReplaceCulture.cs | 70 +++ src/Testing/src/TaskExtensions.cs | 64 +++ src/Testing/src/TestPathUtilities.cs | 31 ++ src/Testing/src/TestPlatformHelper.cs | 23 + .../src/Tracing/CollectingEventListener.cs | 60 +++ src/Testing/src/Tracing/EventAssert.cs | 60 +++ .../src/Tracing/EventSourceTestBase.cs | 39 ++ .../EventSourceTestCollection.cs | 10 + .../src/xunit/ConditionalFactAttribute.cs | 15 + .../src/xunit/ConditionalFactDiscoverer.cs | 27 ++ .../src/xunit/ConditionalTheoryAttribute.cs | 15 + .../src/xunit/ConditionalTheoryDiscoverer.cs | 47 ++ src/Testing/src/xunit/DockerOnlyAttribute.cs | 38 ++ ...vironmentVariableSkipConditionAttribute.cs | 95 ++++ .../xunit/FrameworkSkipConditionAttribute.cs | 57 +++ src/Testing/src/xunit/IEnvironmentVariable.cs | 10 + src/Testing/src/xunit/ITestCondition.cs | 12 + .../src/xunit/MinimumOsVersionAttribute.cs | 111 +++++ .../src/xunit/OSSkipConditionAttribute.cs | 99 ++++ src/Testing/src/xunit/OperatingSystems.cs | 15 + src/Testing/src/xunit/RuntimeFrameworks.cs | 16 + src/Testing/src/xunit/SkippedTestCase.cs | 40 ++ src/Testing/src/xunit/TestMethodExtensions.cs | 34 ++ src/Testing/src/xunit/WindowsVersions.cs | 18 + .../test/CollectingEventListenerTest.cs | 87 ++++ src/Testing/test/ConditionalFactTest.cs | 60 +++ src/Testing/test/ConditionalTheoryTest.cs | 156 +++++++ src/Testing/test/DockerTests.cs | 21 + .../EnvironmentVariableSkipConditionTest.cs | 166 +++++++ src/Testing/test/ExceptionAssertTest.cs | 39 ++ src/Testing/test/HttpClientSlimTest.cs | 117 +++++ .../Microsoft.AspNetCore.Testing.Tests.csproj | 28 ++ .../test/OSSkipConditionAttributeTest.cs | 132 ++++++ src/Testing/test/OSSkipConditionTest.cs | 116 +++++ .../test/ReplaceCultureAttributeTest.cs | 66 +++ src/Testing/test/TaskExtensionsTest.cs | 18 + src/Testing/test/TestPathUtilitiesTest.cs | 31 ++ src/Testing/test/TestPlatformHelperTest.cs | 55 +++ 64 files changed, 3517 insertions(+), 268 deletions(-) create mode 100644 src/ObjectPool/test/ThreadingTest.cs create mode 100644 src/Shared/BenchmarkRunner/DefaultCoreDebugConfig.cs create mode 100644 src/Shared/BenchmarkRunner/DefaultCorePerfLabConfig.cs create mode 100644 src/Shared/BenchmarkRunner/DefaultCoreProfileConfig.cs create mode 100644 src/Shared/BenchmarkRunner/ParamsDisplayInfoColumn.cs create mode 100644 src/Shared/NonCapturingTimer/NonCapturingTimer.cs create mode 100644 src/Shared/test/Shared.Tests/NonCapturingTimerTest.cs create mode 100644 src/Testing/src/CultureReplacer.cs create mode 100644 src/Testing/src/ExceptionAssertions.cs create mode 100644 src/Testing/src/HttpClientSlim.cs create mode 100644 src/Testing/src/Microsoft.AspNetCore.Testing.csproj create mode 100644 src/Testing/src/Properties/AssemblyInfo.cs create mode 100644 src/Testing/src/ReplaceCulture.cs create mode 100644 src/Testing/src/TaskExtensions.cs create mode 100644 src/Testing/src/TestPathUtilities.cs create mode 100644 src/Testing/src/TestPlatformHelper.cs create mode 100644 src/Testing/src/Tracing/CollectingEventListener.cs create mode 100644 src/Testing/src/Tracing/EventAssert.cs create mode 100644 src/Testing/src/Tracing/EventSourceTestBase.cs create mode 100644 src/Testing/src/contentFiles/cs/netstandard2.0/EventSourceTestCollection.cs create mode 100644 src/Testing/src/xunit/ConditionalFactAttribute.cs create mode 100644 src/Testing/src/xunit/ConditionalFactDiscoverer.cs create mode 100644 src/Testing/src/xunit/ConditionalTheoryAttribute.cs create mode 100644 src/Testing/src/xunit/ConditionalTheoryDiscoverer.cs create mode 100644 src/Testing/src/xunit/DockerOnlyAttribute.cs create mode 100644 src/Testing/src/xunit/EnvironmentVariableSkipConditionAttribute.cs create mode 100644 src/Testing/src/xunit/FrameworkSkipConditionAttribute.cs create mode 100644 src/Testing/src/xunit/IEnvironmentVariable.cs create mode 100644 src/Testing/src/xunit/ITestCondition.cs create mode 100644 src/Testing/src/xunit/MinimumOsVersionAttribute.cs create mode 100644 src/Testing/src/xunit/OSSkipConditionAttribute.cs create mode 100644 src/Testing/src/xunit/OperatingSystems.cs create mode 100644 src/Testing/src/xunit/RuntimeFrameworks.cs create mode 100644 src/Testing/src/xunit/SkippedTestCase.cs create mode 100644 src/Testing/src/xunit/TestMethodExtensions.cs create mode 100644 src/Testing/src/xunit/WindowsVersions.cs create mode 100644 src/Testing/test/CollectingEventListenerTest.cs create mode 100644 src/Testing/test/ConditionalFactTest.cs create mode 100644 src/Testing/test/ConditionalTheoryTest.cs create mode 100644 src/Testing/test/DockerTests.cs create mode 100644 src/Testing/test/EnvironmentVariableSkipConditionTest.cs create mode 100644 src/Testing/test/ExceptionAssertTest.cs create mode 100644 src/Testing/test/HttpClientSlimTest.cs create mode 100644 src/Testing/test/Microsoft.AspNetCore.Testing.Tests.csproj create mode 100644 src/Testing/test/OSSkipConditionAttributeTest.cs create mode 100644 src/Testing/test/OSSkipConditionTest.cs create mode 100644 src/Testing/test/ReplaceCultureAttributeTest.cs create mode 100644 src/Testing/test/TaskExtensionsTest.cs create mode 100644 src/Testing/test/TestPathUtilitiesTest.cs create mode 100644 src/Testing/test/TestPlatformHelperTest.cs diff --git a/src/ObjectPool/src/DefaultObjectPool.cs b/src/ObjectPool/src/DefaultObjectPool.cs index 87825967ac..dcd7f1c715 100644 --- a/src/ObjectPool/src/DefaultObjectPool.cs +++ b/src/ObjectPool/src/DefaultObjectPool.cs @@ -42,8 +42,7 @@ namespace Microsoft.Extensions.ObjectPool public override T Get() { - T item = _firstItem; - + var item = _firstItem; if (item == null || Interlocked.CompareExchange(ref _firstItem, null, item) != item) { item = GetViaScan(); @@ -55,12 +54,10 @@ namespace Microsoft.Extensions.ObjectPool [MethodImpl(MethodImplOptions.AggressiveInlining)] private T GetViaScan() { - ObjectWrapper[] items = _items; - + var items = _items; for (var i = 0; i < items.Length; i++) { - T item = items[i]; - + var item = items[i].Element; if (item != null && Interlocked.CompareExchange(ref items[i].Element, null, item) == item) { return item; @@ -88,21 +85,17 @@ namespace Microsoft.Extensions.ObjectPool [MethodImpl(MethodImplOptions.AggressiveInlining)] private void ReturnViaScan(T obj) { - ObjectWrapper[] items = _items; - + var items = _items; for (var i = 0; i < items.Length && Interlocked.CompareExchange(ref items[i].Element, obj, null) != null; ++i) { } } + // PERF: the struct wrapper avoids array-covariance-checks from the runtime when assigning to elements of the array. [DebuggerDisplay("{Element}")] private struct ObjectWrapper { public T Element; - - public ObjectWrapper(T item) => Element = item; - - public static implicit operator T(ObjectWrapper wrapper) => wrapper.Element; } } } diff --git a/src/ObjectPool/test/DefaultObjectPoolTest.cs b/src/ObjectPool/test/DefaultObjectPoolTest.cs index ca14a3f53b..b44aa7e1c7 100644 --- a/src/ObjectPool/test/DefaultObjectPoolTest.cs +++ b/src/ObjectPool/test/DefaultObjectPoolTest.cs @@ -1,12 +1,10 @@ // 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.Collections.Generic; -using System.Threading.Tasks; using Xunit; -namespace Microsoft.Extensions.ObjectPool.Test +namespace Microsoft.Extensions.ObjectPool { public class DefaultObjectPoolTest { diff --git a/src/ObjectPool/test/ThreadingTest.cs b/src/ObjectPool/test/ThreadingTest.cs new file mode 100644 index 0000000000..541bc5ffd4 --- /dev/null +++ b/src/ObjectPool/test/ThreadingTest.cs @@ -0,0 +1,80 @@ +// 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.Threading; +using Xunit; + +namespace Microsoft.Extensions.ObjectPool +{ + public class ThreadingTest + { + private CancellationTokenSource _cts; + private DefaultObjectPool _pool; + private bool _foundError; + + [Fact] + public void RunThreadingTest() + { + _cts = new CancellationTokenSource(); + _pool = new DefaultObjectPool(new DefaultPooledObjectPolicy(), 10); + + var threads = new Thread[8]; + for (var i = 0; i < threads.Length; i++) + { + threads[i] = new Thread(Run); + } + + for (var i = 0; i < threads.Length; i++) + { + threads[i].Start(); + } + + // Run for 1000ms + _cts.CancelAfter(1000); + + // Wait for all threads to complete + for (var i = 0; i < threads.Length; i++) + { + threads[i].Join(); + } + + Assert.False(_foundError, "Race condition found. An item was shared across threads."); + } + + private void Run() + { + while (!_cts.IsCancellationRequested) + { + var obj = _pool.Get(); + if (obj.i != 0) + { + _foundError = true; + } + obj.i = 123; + + var obj2 = _pool.Get(); + if (obj2.i != 0) + { + _foundError = true; + } + obj2.i = 321; + + obj.Reset(); + _pool.Return(obj); + + obj2.Reset(); + _pool.Return(obj2); + } + } + + private class Item + { + public int i = 0; + + public void Reset() + { + i = 0; + } + } + } +} diff --git a/src/Shared/BenchmarkRunner/AspNetCoreBenchmarkAttribute.cs b/src/Shared/BenchmarkRunner/AspNetCoreBenchmarkAttribute.cs index a4044d1b5e..d16493a738 100644 --- a/src/Shared/BenchmarkRunner/AspNetCoreBenchmarkAttribute.cs +++ b/src/Shared/BenchmarkRunner/AspNetCoreBenchmarkAttribute.cs @@ -2,38 +2,72 @@ // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; -using System.Linq; -using System.Reflection; -using BenchmarkDotNet.Attributes; -using BenchmarkDotNet.Running; +using System.Collections.Generic; using BenchmarkDotNet.Configs; -using BenchmarkDotNet.Jobs; -using BenchmarkDotNet.Toolchains.InProcess; namespace BenchmarkDotNet.Attributes { [AttributeUsage(AttributeTargets.Class | AttributeTargets.Assembly)] internal class AspNetCoreBenchmarkAttribute : Attribute, IConfigSource { - public static bool UseValidationConfig { get; set; } - - public Type ConfigType { get; } - public Type ValidationConfigType { get; } - - public AspNetCoreBenchmarkAttribute() : this(typeof(DefaultCoreConfig)) + public AspNetCoreBenchmarkAttribute() + : this(typeof(DefaultCoreConfig)) { } - public AspNetCoreBenchmarkAttribute(Type configType) : this(configType, typeof(DefaultCoreValidationConfig)) + public AspNetCoreBenchmarkAttribute(Type configType) + : this(configType, typeof(DefaultCoreValidationConfig)) { } public AspNetCoreBenchmarkAttribute(Type configType, Type validationConfigType) { - ConfigType = configType; - ValidationConfigType = validationConfigType; + ConfigTypes = new Dictionary() + { + { NamedConfiguration.Default, typeof(DefaultCoreConfig) }, + { NamedConfiguration.Validation, typeof(DefaultCoreValidationConfig) }, + { NamedConfiguration.Profile, typeof(DefaultCoreProfileConfig) }, + { NamedConfiguration.Debug, typeof(DefaultCoreDebugConfig) }, + { NamedConfiguration.PerfLab, typeof(DefaultCorePerfLabConfig) }, + }; + + if (configType != null) + { + ConfigTypes[NamedConfiguration.Default] = configType; + } + + if (validationConfigType != null) + { + ConfigTypes[NamedConfiguration.Validation] = validationConfigType; + } } - public IConfig Config => (IConfig) Activator.CreateInstance(UseValidationConfig ? ValidationConfigType : ConfigType, Array.Empty()); + public IConfig Config + { + get + { + if (!ConfigTypes.TryGetValue(ConfigName ?? NamedConfiguration.Default, out var configType)) + { + var message = $"Could not find a configuration matching {ConfigName}. " + + $"Known configurations: {string.Join(", ", ConfigTypes.Keys)}"; + throw new InvalidOperationException(message); + } + + return (IConfig)Activator.CreateInstance(configType, Array.Empty()); + } + } + + public Dictionary ConfigTypes { get; } + + public static string ConfigName { get; set; } = NamedConfiguration.Default; + + public static class NamedConfiguration + { + public static readonly string Default = "default"; + public static readonly string Validation = "validation"; + public static readonly string Profile = "profile"; + public static readonly string Debug = "debug"; + public static readonly string PerfLab = "perflab"; + } } } diff --git a/src/Shared/BenchmarkRunner/DefaultCoreConfig.cs b/src/Shared/BenchmarkRunner/DefaultCoreConfig.cs index a8d9d60536..5e2bafd506 100644 --- a/src/Shared/BenchmarkRunner/DefaultCoreConfig.cs +++ b/src/Shared/BenchmarkRunner/DefaultCoreConfig.cs @@ -28,7 +28,11 @@ namespace BenchmarkDotNet.Attributes Add(JitOptimizationsValidator.FailOnError); Add(Job.Core +#if NETCOREAPP2_1 .With(CsProjCoreToolchain.From(NetCoreAppSettings.NetCoreApp21)) +#else + .With(CsProjCoreToolchain.From(new NetCoreAppSettings("netcoreapp2.2", null, ".NET Core 2.2"))) +#endif .With(new GcMode { Server = true }) .With(RunStrategy.Throughput)); } diff --git a/src/Shared/BenchmarkRunner/DefaultCoreDebugConfig.cs b/src/Shared/BenchmarkRunner/DefaultCoreDebugConfig.cs new file mode 100644 index 0000000000..f51bed2db9 --- /dev/null +++ b/src/Shared/BenchmarkRunner/DefaultCoreDebugConfig.cs @@ -0,0 +1,23 @@ +// 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 BenchmarkDotNet.Configs; +using BenchmarkDotNet.Engines; +using BenchmarkDotNet.Jobs; +using BenchmarkDotNet.Loggers; +using BenchmarkDotNet.Validators; + +namespace BenchmarkDotNet.Attributes +{ + internal class DefaultCoreDebugConfig : ManualConfig + { + public DefaultCoreDebugConfig() + { + Add(ConsoleLogger.Default); + Add(JitOptimizationsValidator.DontFailOnError); + + Add(Job.InProcess + .With(RunStrategy.Throughput)); + } + } +} diff --git a/src/Shared/BenchmarkRunner/DefaultCorePerfLabConfig.cs b/src/Shared/BenchmarkRunner/DefaultCorePerfLabConfig.cs new file mode 100644 index 0000000000..5cc809e166 --- /dev/null +++ b/src/Shared/BenchmarkRunner/DefaultCorePerfLabConfig.cs @@ -0,0 +1,48 @@ +// 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 BenchmarkDotNet.Columns; +using BenchmarkDotNet.Configs; +using BenchmarkDotNet.Diagnosers; +using BenchmarkDotNet.Engines; +using BenchmarkDotNet.Exporters; +using BenchmarkDotNet.Exporters.Csv; +using BenchmarkDotNet.Jobs; +using BenchmarkDotNet.Loggers; +using BenchmarkDotNet.Validators; + +namespace BenchmarkDotNet.Attributes +{ + internal class DefaultCorePerfLabConfig : ManualConfig + { + public DefaultCorePerfLabConfig() + { + Add(ConsoleLogger.Default); + + Add(MemoryDiagnoser.Default); + Add(StatisticColumn.OperationsPerSecond); + Add(new ParamsSummaryColumn()); + Add(DefaultColumnProviders.Statistics, DefaultColumnProviders.Diagnosers, DefaultColumnProviders.Target); + + // TODO: When upgrading to BDN 0.11.1, use Add(DefaultColumnProviders.Descriptor); + // DefaultColumnProviders.Target is deprecated + + Add(JitOptimizationsValidator.FailOnError); + + Add(Job.InProcess + .With(RunStrategy.Throughput)); + + Add(MarkdownExporter.GitHub); + + Add(new CsvExporter( + CsvSeparator.Comma, + new Reports.SummaryStyle + { + PrintUnitsInHeader = true, + PrintUnitsInContent = false, + TimeUnit = Horology.TimeUnit.Microsecond, + SizeUnit = SizeUnit.KB + })); + } + } +} diff --git a/src/Shared/BenchmarkRunner/DefaultCoreProfileConfig.cs b/src/Shared/BenchmarkRunner/DefaultCoreProfileConfig.cs new file mode 100644 index 0000000000..1b59cb89c5 --- /dev/null +++ b/src/Shared/BenchmarkRunner/DefaultCoreProfileConfig.cs @@ -0,0 +1,32 @@ +// 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 BenchmarkDotNet.Columns; +using BenchmarkDotNet.Configs; +using BenchmarkDotNet.Diagnosers; +using BenchmarkDotNet.Engines; +using BenchmarkDotNet.Exporters; +using BenchmarkDotNet.Jobs; +using BenchmarkDotNet.Loggers; +using BenchmarkDotNet.Validators; + +namespace BenchmarkDotNet.Attributes +{ + internal class DefaultCoreProfileConfig : ManualConfig + { + public DefaultCoreProfileConfig() + { + Add(ConsoleLogger.Default); + Add(MarkdownExporter.GitHub); + + Add(MemoryDiagnoser.Default); + Add(StatisticColumn.OperationsPerSecond); + Add(DefaultColumnProviders.Instance); + + Add(JitOptimizationsValidator.FailOnError); + + Add(Job.InProcess + .With(RunStrategy.Throughput)); + } + } +} diff --git a/src/Shared/BenchmarkRunner/ParamsDisplayInfoColumn.cs b/src/Shared/BenchmarkRunner/ParamsDisplayInfoColumn.cs new file mode 100644 index 0000000000..b246e21c2e --- /dev/null +++ b/src/Shared/BenchmarkRunner/ParamsDisplayInfoColumn.cs @@ -0,0 +1,26 @@ +// 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 BenchmarkDotNet.Columns; +using BenchmarkDotNet.Reports; +using BenchmarkDotNet.Running; + +namespace BenchmarkDotNet.Attributes +{ + public class ParamsSummaryColumn : IColumn + { + public string Id => nameof(ParamsSummaryColumn); + public string ColumnName { get; } = "Params"; + public bool IsDefault(Summary summary, Benchmark benchmark) => false; + public string GetValue(Summary summary, Benchmark benchmark) => benchmark.Parameters.DisplayInfo; + public bool IsAvailable(Summary summary) => true; + public bool AlwaysShow => true; + public ColumnCategory Category => ColumnCategory.Params; + public int PriorityInCategory => 0; + public override string ToString() => ColumnName; + public bool IsNumeric => false; + public UnitType UnitType => UnitType.Dimensionless; + public string GetValue(Summary summary, Benchmark benchmark, ISummaryStyle style) => GetValue(summary, benchmark); + public string Legend => $"Summary of all parameter values"; + } +} \ No newline at end of file diff --git a/src/Shared/BenchmarkRunner/Program.cs b/src/Shared/BenchmarkRunner/Program.cs index 3297d5dae9..a1db1a50e8 100644 --- a/src/Shared/BenchmarkRunner/Program.cs +++ b/src/Shared/BenchmarkRunner/Program.cs @@ -2,15 +2,14 @@ // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; +using System.Diagnostics; using System.IO; using System.Linq; using System.Reflection; using System.Text; using BenchmarkDotNet.Attributes; -using BenchmarkDotNet.Running; using BenchmarkDotNet.Configs; -using BenchmarkDotNet.Jobs; -using BenchmarkDotNet.Toolchains.InProcess; +using BenchmarkDotNet.Running; namespace Microsoft.AspNetCore.BenchmarkDotNet.Runner { @@ -25,7 +24,7 @@ namespace Microsoft.AspNetCore.BenchmarkDotNet.Runner { BeforeMain(args); - CheckValidate(ref args); + AssignConfiguration(ref args); var summaries = BenchmarkSwitcher.FromAssembly(typeof(Program).GetTypeInfo().Assembly) .Run(args, ManualConfig.CreateEmpty()); @@ -66,16 +65,35 @@ namespace Microsoft.AspNetCore.BenchmarkDotNet.Runner return 1; } - private static void CheckValidate(ref string[] args) + private static void AssignConfiguration(ref string[] args) { var argsList = args.ToList(); if (argsList.Remove("--validate") || argsList.Remove("--validate-fast")) { + // Compat: support the old style of passing a config that is used by our build system. SuppressConsole(); - AspNetCoreBenchmarkAttribute.UseValidationConfig = true; + AspNetCoreBenchmarkAttribute.ConfigName = AspNetCoreBenchmarkAttribute.NamedConfiguration.Validation; + args = argsList.ToArray(); + return; + } + + var index = argsList.IndexOf("--config"); + if (index >= 0 && index < argsList.Count -1) + { + AspNetCoreBenchmarkAttribute.ConfigName = argsList[index + 1]; + argsList.RemoveAt(index + 1); + argsList.RemoveAt(index); + args = argsList.ToArray(); + return; } - args = argsList.ToArray(); + if (Debugger.IsAttached) + { + Console.WriteLine("Using the debug config since you are debugging. I hope that's OK!"); + Console.WriteLine("Specify a configuration with --config to override"); + AspNetCoreBenchmarkAttribute.ConfigName = AspNetCoreBenchmarkAttribute.NamedConfiguration.Debug; + return; + } } private static void SuppressConsole() diff --git a/src/Shared/CertificateGeneration/CertificateManager.cs b/src/Shared/CertificateGeneration/CertificateManager.cs index 4e2a0a9964..952cf7c36d 100644 --- a/src/Shared/CertificateGeneration/CertificateManager.cs +++ b/src/Shared/CertificateGeneration/CertificateManager.cs @@ -19,17 +19,12 @@ namespace Microsoft.AspNetCore.Certificates.Generation public const string AspNetHttpsOid = "1.3.6.1.4.1.311.84.1.1"; public const string AspNetHttpsOidFriendlyName = "ASP.NET Core HTTPS development certificate"; - public const string AspNetIdentityOid = "1.3.6.1.4.1.311.84.1.2"; - public const string AspNetIdentityOidFriendlyName = "ASP.NET Core Identity Json Web Token signing development certificate"; - private const string ServerAuthenticationEnhancedKeyUsageOid = "1.3.6.1.5.5.7.3.1"; private const string ServerAuthenticationEnhancedKeyUsageOidFriendlyName = "Server Authentication"; private const string LocalhostHttpsDnsName = "localhost"; private const string LocalhostHttpsDistinguishedName = "CN=" + LocalhostHttpsDnsName; - private const string IdentityDistinguishedName = "CN=Microsoft.AspNetCore.Identity.Signing"; - public const int RSAMinimumKeySizeInBits = 2048; private static readonly TimeSpan MaxRegexTimeout = TimeSpan.FromMinutes(1); @@ -37,7 +32,7 @@ namespace Microsoft.AspNetCore.Certificates.Generation private const string MacOSSystemKeyChain = "/Library/Keychains/System.keychain"; private static readonly string MacOSUserKeyChain = Environment.GetEnvironmentVariable("HOME") + "/Library/Keychains/login.keychain-db"; private const string MacOSFindCertificateCommandLine = "security"; -#if NETCOREAPP2_0 || NETCOREAPP2_1 +#if NETCOREAPP2_0 || NETCOREAPP2_1 || NETCOREAPP2_2 private static readonly string MacOSFindCertificateCommandLineArgumentsFormat = "find-certificate -c {0} -a -Z -p " + MacOSSystemKeyChain; #endif private const string MacOSFindCertificateOutputRegex = "SHA-1 hash: ([0-9A-Z]+)"; @@ -46,7 +41,7 @@ namespace Microsoft.AspNetCore.Certificates.Generation private const string MacOSDeleteCertificateCommandLine = "sudo"; private const string MacOSDeleteCertificateCommandLineArgumentsFormat = "security delete-certificate -Z {0} {1}"; private const string MacOSTrustCertificateCommandLine = "sudo"; -#if NETCOREAPP2_0 || NETCOREAPP2_1 +#if NETCOREAPP2_0 || NETCOREAPP2_1 || NETCOREAPP2_2 private static readonly string MacOSTrustCertificateCommandLineArguments = "security add-trusted-cert -d -r trustRoot -k " + MacOSSystemKeyChain + " "; #endif private const int UserCancelledErrorCode = 1223; @@ -56,8 +51,10 @@ namespace Microsoft.AspNetCore.Certificates.Generation StoreName storeName, StoreLocation location, bool isValid, - bool requireExportable = true) + bool requireExportable = true, + DiagnosticInformation diagnostics = null) { + diagnostics?.Debug($"Listing '{purpose.ToString()}' certificates on '{location}\\{storeName}'."); var certificates = new List(); try { @@ -70,28 +67,37 @@ namespace Microsoft.AspNetCore.Certificates.Generation { case CertificatePurpose.All: matchingCertificates = matchingCertificates - .Where(c => HasOid(c, AspNetHttpsOid) || HasOid(c, AspNetIdentityOid)); + .Where(c => HasOid(c, AspNetHttpsOid)); break; case CertificatePurpose.HTTPS: matchingCertificates = matchingCertificates .Where(c => HasOid(c, AspNetHttpsOid)); break; - case CertificatePurpose.Signing: - matchingCertificates = matchingCertificates - .Where(c => HasOid(c, AspNetIdentityOid)); - break; default: break; } + + diagnostics?.Debug(diagnostics.DescribeCertificates(matchingCertificates)); if (isValid) { // Ensure the certificate hasn't expired, has a private key and its exportable // (for container/unix scenarios). + diagnostics?.Debug("Checking certificates for validity."); var now = DateTimeOffset.Now; - matchingCertificates = matchingCertificates + var validCertificates = matchingCertificates .Where(c => c.NotBefore <= now && now <= c.NotAfter && - (!requireExportable || !RuntimeInformation.IsOSPlatform(OSPlatform.Windows) || IsExportable(c))); + (!requireExportable || !RuntimeInformation.IsOSPlatform(OSPlatform.Windows) || IsExportable(c))) + .ToArray(); + + var invalidCertificates = matchingCertificates.Except(validCertificates); + + diagnostics?.Debug("Listing valid certificates"); + diagnostics?.Debug(diagnostics.DescribeCertificates(validCertificates)); + diagnostics?.Debug("Listing invalid certificates"); + diagnostics?.Debug(diagnostics.DescribeCertificates(invalidCertificates)); + + matchingCertificates = validCertificates; } // We need to enumerate the certificates early to prevent dispoisng issues. @@ -123,7 +129,7 @@ namespace Microsoft.AspNetCore.Certificates.Generation cngPrivateKey.Key.ExportPolicy == CngExportPolicies.AllowExport)); #else // Only check for RSA CryptoServiceProvider and do not fail in XPlat tooling as - // System.Security.Cryptography.Cng is not pat of the shared framework and we don't + // System.Security.Cryptography.Cng is not part of the shared framework and we don't // want to bring the dependency in on CLI scenarios. This functionality will be used // on CLI scenarios as part of the first run experience, so checking the exportability // of the certificate is not important. @@ -133,7 +139,7 @@ namespace Microsoft.AspNetCore.Certificates.Generation #endif } - private void DisposeCertificates(IEnumerable disposables) + private static void DisposeCertificates(IEnumerable disposables) { foreach (var disposable in disposables) { @@ -147,9 +153,9 @@ namespace Microsoft.AspNetCore.Certificates.Generation } } -#if NETCOREAPP2_0 || NETCOREAPP2_1 +#if NETCOREAPP2_0 || NETCOREAPP2_1 || NETCOREAPP2_2 - public X509Certificate2 CreateAspNetCoreHttpsDevelopmentCertificate(DateTimeOffset notBefore, DateTimeOffset notAfter, string subjectOverride) + public X509Certificate2 CreateAspNetCoreHttpsDevelopmentCertificate(DateTimeOffset notBefore, DateTimeOffset notAfter, string subjectOverride, DiagnosticInformation diagnostics = null) { var subject = new X500DistinguishedName(subjectOverride ?? LocalhostHttpsDistinguishedName); var extensions = new List(); @@ -192,46 +198,6 @@ namespace Microsoft.AspNetCore.Certificates.Generation return certificate; } - public X509Certificate2 CreateApplicationTokenSigningDevelopmentCertificate(DateTimeOffset notBefore, DateTimeOffset notAfter, string subjectOverride) - { - var subject = new X500DistinguishedName(subjectOverride ?? IdentityDistinguishedName); - var extensions = new List(); - - var keyUsage = new X509KeyUsageExtension(X509KeyUsageFlags.DigitalSignature, critical: true); - var enhancedKeyUsage = new X509EnhancedKeyUsageExtension( - new OidCollection() { - new Oid( - ServerAuthenticationEnhancedKeyUsageOid, - ServerAuthenticationEnhancedKeyUsageOidFriendlyName) - }, - critical: true); - - var basicConstraints = new X509BasicConstraintsExtension( - certificateAuthority: false, - hasPathLengthConstraint: false, - pathLengthConstraint: 0, - critical: true); - - var aspNetIdentityExtension = new X509Extension( - new AsnEncodedData( - new Oid(AspNetIdentityOid, AspNetIdentityOidFriendlyName), - Encoding.ASCII.GetBytes(AspNetIdentityOidFriendlyName)), - critical: false); - - extensions.Add(basicConstraints); - extensions.Add(keyUsage); - extensions.Add(enhancedKeyUsage); - extensions.Add(aspNetIdentityExtension); - - var certificate = CreateSelfSignedCertificate(subject, extensions, notBefore, notAfter); - if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) - { - certificate.FriendlyName = AspNetIdentityOidFriendlyName; - } - - return certificate; - } - public X509Certificate2 CreateSelfSignedCertificate( X500DistinguishedName subject, IEnumerable extensions, @@ -260,8 +226,9 @@ namespace Microsoft.AspNetCore.Certificates.Generation } } - public X509Certificate2 SaveCertificateInStore(X509Certificate2 certificate, StoreName name, StoreLocation location) + public X509Certificate2 SaveCertificateInStore(X509Certificate2 certificate, StoreName name, StoreLocation location, DiagnosticInformation diagnostics = null) { + diagnostics?.Debug("Saving the certificate into the certificate store."); var imported = certificate; if (!RuntimeInformation.IsOSPlatform(OSPlatform.OSX)) { @@ -287,33 +254,67 @@ namespace Microsoft.AspNetCore.Certificates.Generation return imported; } - public void ExportCertificate(X509Certificate2 certificate, string path, bool includePrivateKey, string password) + public void ExportCertificate(X509Certificate2 certificate, string path, bool includePrivateKey, string password, DiagnosticInformation diagnostics = null) { - if (Path.GetDirectoryName(path) != "") + diagnostics?.Debug( + $"Exporting certificate to '{path}'", + includePrivateKey ? "The certificate will contain the private key" : "The certificate will not contain the private key"); + if (includePrivateKey && password == null) { - Directory.CreateDirectory(Path.GetDirectoryName(path)); + diagnostics?.Debug("No password was provided for the certificate."); } + var targetDirectoryPath = Path.GetDirectoryName(path); + if (targetDirectoryPath != "") + { + diagnostics?.Debug($"Ensuring that the directory for the target exported certificate path exists '{targetDirectoryPath}'"); + Directory.CreateDirectory(targetDirectoryPath); + } + + byte[] bytes; if (includePrivateKey) { - var bytes = certificate.Export(X509ContentType.Pkcs12, password); try { - File.WriteAllBytes(path, bytes); + diagnostics?.Debug($"Exporting the certificate including the private key."); + bytes = certificate.Export(X509ContentType.Pkcs12, password); } - finally + catch (Exception e) { - Array.Clear(bytes, 0, bytes.Length); + diagnostics?.Error($"Failed to export the certificate with the private key", e); + throw; } } else { - var bytes = certificate.Export(X509ContentType.Cert); + try + { + diagnostics?.Debug($"Exporting the certificate without the private key."); + bytes = certificate.Export(X509ContentType.Cert); + } + catch (Exception ex) + { + diagnostics?.Error($"Failed to export the certificate without the private key", ex); + throw; + } + } + try + { + diagnostics?.Debug($"Writing exported certificate to path '{path}'."); File.WriteAllBytes(path, bytes); } + catch (Exception ex) + { + diagnostics?.Error("Failed writing the certificate to the target path", ex); + throw; + } + finally + { + Array.Clear(bytes, 0, bytes.Length); + } } - public void TrustCertificate(X509Certificate2 certificate) + public void TrustCertificate(X509Certificate2 certificate, DiagnosticInformation diagnostics = null) { // Strip certificate of the private key if any. var publicCertificate = new X509Certificate2(certificate.Export(X509ContentType.Cert)); @@ -322,21 +323,24 @@ namespace Microsoft.AspNetCore.Certificates.Generation { if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) { - TrustCertificateOnWindows(certificate, publicCertificate); + diagnostics?.Debug("Trusting the certificate on Windows."); + TrustCertificateOnWindows(certificate, publicCertificate, diagnostics); } else if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX)) { - TrustCertificateOnMac(publicCertificate); + diagnostics?.Debug("Trusting the certificate on MAC."); + TrustCertificateOnMac(publicCertificate, diagnostics); } } } - private void TrustCertificateOnMac(X509Certificate2 publicCertificate) + private void TrustCertificateOnMac(X509Certificate2 publicCertificate, DiagnosticInformation diagnostics) { var tmpFile = Path.GetTempFileName(); try { ExportCertificate(publicCertificate, tmpFile, includePrivateKey: false, password: null); + diagnostics?.Debug("Running the trust command on Mac OS"); using (var process = Process.Start(MacOSTrustCertificateCommandLine, MacOSTrustCertificateCommandLineArguments + tmpFile)) { process.WaitForExit(); @@ -362,19 +366,29 @@ namespace Microsoft.AspNetCore.Certificates.Generation } } - private static void TrustCertificateOnWindows(X509Certificate2 certificate, X509Certificate2 publicCertificate) + private static void TrustCertificateOnWindows(X509Certificate2 certificate, X509Certificate2 publicCertificate, DiagnosticInformation diagnostics = null) { publicCertificate.FriendlyName = certificate.FriendlyName; using (var store = new X509Store(StoreName.Root, StoreLocation.CurrentUser)) { store.Open(OpenFlags.ReadWrite); + var existing = store.Certificates.Find(X509FindType.FindByThumbprint, publicCertificate.Thumbprint, validOnly: false); + if (existing.Count > 0) + { + diagnostics?.Debug("Certificate already trusted. Skipping trust step."); + DisposeCertificates(existing.OfType()); + return; + } + try { + diagnostics?.Debug("Adding certificate to the store."); store.Add(publicCertificate); } catch (CryptographicException exception) when (exception.HResult == UserCancelledErrorCode) { + diagnostics?.Debug("User cancelled the trust prompt."); throw new UserCancelledTrustException(); } store.Close(); @@ -437,6 +451,30 @@ namespace Microsoft.AspNetCore.Certificates.Generation } } + public DiagnosticInformation CleanupHttpsCertificates2(string subject = LocalhostHttpsDistinguishedName) + { + return CleanupCertificates2(CertificatePurpose.HTTPS, subject); + } + + public DiagnosticInformation CleanupCertificates2(CertificatePurpose purpose, string subject) + { + var diagnostics = new DiagnosticInformation(); + // On OS X we don't have a good way to manage trusted certificates in the system keychain + // so we do everything by invoking the native toolchain. + // This has some limitations, like for example not being able to identify our custom OID extension. For that + // matter, when we are cleaning up certificates on the machine, we start by removing the trusted certificates. + // To do this, we list the certificates that we can identify on the current user personal store and we invoke + // the native toolchain to remove them from the sytem keychain. Once we have removed the trusted certificates, + // we remove the certificates from the local user store to finish up the cleanup. + var certificates = ListCertificates(purpose, StoreName.My, StoreLocation.CurrentUser, isValid: false, requireExportable: true, diagnostics); + foreach (var certificate in certificates) + { + RemoveCertificate(certificate, RemoveLocations.All, diagnostics); + } + + return diagnostics; + } + public void RemoveAllCertificates(CertificatePurpose purpose, StoreName storeName, StoreLocation storeLocation, string subject = null) { var certificates = RuntimeInformation.IsOSPlatform(OSPlatform.OSX) ? @@ -454,32 +492,33 @@ namespace Microsoft.AspNetCore.Certificates.Generation DisposeCertificates(certificates); } - private void RemoveCertificate(X509Certificate2 certificate, RemoveLocations locations) + private void RemoveCertificate(X509Certificate2 certificate, RemoveLocations locations, DiagnosticInformation diagnostics = null) { switch (locations) { case RemoveLocations.Undefined: throw new InvalidOperationException($"'{nameof(RemoveLocations.Undefined)}' is not a valid location."); case RemoveLocations.Local: - RemoveCertificateFromUserStore(certificate); + RemoveCertificateFromUserStore(certificate, diagnostics); break; case RemoveLocations.Trusted when !RuntimeInformation.IsOSPlatform(OSPlatform.Linux): - RemoveCertificateFromTrustedRoots(certificate); + RemoveCertificateFromTrustedRoots(certificate, diagnostics); break; case RemoveLocations.All: if (!RuntimeInformation.IsOSPlatform(OSPlatform.Linux)) { - RemoveCertificateFromTrustedRoots(certificate); + RemoveCertificateFromTrustedRoots(certificate, diagnostics); } - RemoveCertificateFromUserStore(certificate); + RemoveCertificateFromUserStore(certificate, diagnostics); break; default: throw new InvalidOperationException("Invalid location."); } } - private static void RemoveCertificateFromUserStore(X509Certificate2 certificate) + private static void RemoveCertificateFromUserStore(X509Certificate2 certificate, DiagnosticInformation diagnostics) { + diagnostics?.Debug($"Trying to remove certificate with thumbprint '{certificate.Thumbprint}' from certificate store '{StoreLocation.CurrentUser}\\{StoreName.My}'."); using (var store = new X509Store(StoreName.My, StoreLocation.CurrentUser)) { store.Open(OpenFlags.ReadWrite); @@ -492,8 +531,9 @@ namespace Microsoft.AspNetCore.Certificates.Generation } } - private void RemoveCertificateFromTrustedRoots(X509Certificate2 certificate) + private void RemoveCertificateFromTrustedRoots(X509Certificate2 certificate, DiagnosticInformation diagnostics) { + diagnostics?.Debug($"Trying to remove certificate with thumbprint '{certificate.Thumbprint}' from certificate store '{StoreLocation.CurrentUser}\\{StoreName.Root}'."); if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) { using (var store = new X509Store(StoreName.Root, StoreLocation.CurrentUser)) @@ -501,9 +541,13 @@ namespace Microsoft.AspNetCore.Certificates.Generation store.Open(OpenFlags.ReadWrite); var matching = store.Certificates .OfType() - .Single(c => c.SerialNumber == certificate.SerialNumber); + .SingleOrDefault(c => c.SerialNumber == certificate.SerialNumber); + + if (matching != null) + { + store.Remove(matching); + } - store.Remove(matching); store.Close(); } } @@ -513,10 +557,12 @@ namespace Microsoft.AspNetCore.Certificates.Generation { try { + diagnostics?.Debug("Trying to remove the certificate trust rule."); RemoveCertificateTrustRule(certificate); } catch { + diagnostics?.Debug("Failed to remove the certificate trust rule."); // We don't care if we fail to remove the trust rule if // for some reason the certificate became untrusted. // The delete command will fail if the certificate is @@ -524,6 +570,10 @@ namespace Microsoft.AspNetCore.Certificates.Generation } RemoveCertificateFromKeyChain(MacOSSystemKeyChain, certificate); } + else + { + diagnostics?.Debug("The certificate was not trusted."); + } } } @@ -601,18 +651,6 @@ namespace Microsoft.AspNetCore.Certificates.Generation return EnsureValidCertificateExists(notBefore, notAfter, CertificatePurpose.HTTPS, path, trust, includePrivateKey, password, subject); } - public EnsureCertificateResult EnsureAspNetCoreApplicationTokensDevelopmentCertificate( - DateTimeOffset notBefore, - DateTimeOffset notAfter, - string path = null, - bool trust = false, - bool includePrivateKey = false, - string password = null, - string subject = IdentityDistinguishedName) - { - return EnsureValidCertificateExists(notBefore, notAfter, CertificatePurpose.Signing, path, trust, includePrivateKey, password, subject); - } - public EnsureCertificateResult EnsureValidCertificateExists( DateTimeOffset notBefore, DateTimeOffset notAfter, @@ -652,9 +690,6 @@ namespace Microsoft.AspNetCore.Certificates.Generation case CertificatePurpose.HTTPS: certificate = CreateAspNetCoreHttpsDevelopmentCertificate(notBefore, notAfter, subjectOverride); break; - case CertificatePurpose.Signing: - certificate = CreateApplicationTokenSigningDevelopmentCertificate(notBefore, notAfter, subjectOverride); - break; default: throw new InvalidOperationException("The certificate must have a purpose."); } @@ -704,6 +739,143 @@ namespace Microsoft.AspNetCore.Certificates.Generation return result; } + // This is just to avoid breaking changes across repos. + // Will be renamed back to EnsureAspNetCoreHttpsDevelopmentCertificate once updates are made elsewhere. + public DetailedEnsureCertificateResult EnsureAspNetCoreHttpsDevelopmentCertificate2( + DateTimeOffset notBefore, + DateTimeOffset notAfter, + string path = null, + bool trust = false, + bool includePrivateKey = false, + string password = null, + string subject = LocalhostHttpsDistinguishedName) + { + return EnsureValidCertificateExists2(notBefore, notAfter, CertificatePurpose.HTTPS, path, trust, includePrivateKey, password, subject); + } + + public DetailedEnsureCertificateResult EnsureValidCertificateExists2( + DateTimeOffset notBefore, + DateTimeOffset notAfter, + CertificatePurpose purpose, + string path, + bool trust, + bool includePrivateKey, + string password, + string subject) + { + if (purpose == CertificatePurpose.All) + { + throw new ArgumentException("The certificate must have a specific purpose."); + } + + var result = new DetailedEnsureCertificateResult(); + + var certificates = ListCertificates(purpose, StoreName.My, StoreLocation.CurrentUser, isValid: true, requireExportable: true, result.Diagnostics).Concat( + ListCertificates(purpose, StoreName.My, StoreLocation.LocalMachine, isValid: true, requireExportable: true, result.Diagnostics)); + + var filteredCertificates = subject == null ? certificates : certificates.Where(c => c.Subject == subject); + if (subject != null) + { + var excludedCertificates = certificates.Except(filteredCertificates); + + result.Diagnostics.Debug($"Filtering found certificates to those with a subject equal to '{subject}'"); + result.Diagnostics.Debug(result.Diagnostics.DescribeCertificates(filteredCertificates)); + result.Diagnostics.Debug($"Listing certificates excluded from consideration."); + result.Diagnostics.Debug(result.Diagnostics.DescribeCertificates(excludedCertificates)); + } + else + { + result.Diagnostics.Debug("Skipped filtering certificates by subject."); + } + + certificates = filteredCertificates; + + result.ResultCode = EnsureCertificateResult.Succeeded; + + X509Certificate2 certificate = null; + if (certificates.Count() > 0) + { + result.Diagnostics.Debug("Found valid certificates present on the machine."); + result.Diagnostics.Debug(result.Diagnostics.DescribeCertificates(certificates)); + certificate = certificates.First(); + result.Diagnostics.Debug("Selected certificate"); + result.Diagnostics.Debug(result.Diagnostics.DescribeCertificates(certificate)); + result.ResultCode = EnsureCertificateResult.ValidCertificatePresent; + } + else + { + result.Diagnostics.Debug("No valid certificates present on this machine. Trying to create one."); + try + { + switch (purpose) + { + case CertificatePurpose.All: + throw new InvalidOperationException("The certificate must have a specific purpose."); + case CertificatePurpose.HTTPS: + certificate = CreateAspNetCoreHttpsDevelopmentCertificate(notBefore, notAfter, subject, result.Diagnostics); + break; + default: + throw new InvalidOperationException("The certificate must have a purpose."); + } + } + catch (Exception e) + { + result.Diagnostics.Error("Error creating the certificate.", e); + result.ResultCode = EnsureCertificateResult.ErrorCreatingTheCertificate; + return result; + } + + try + { + certificate = SaveCertificateInStore(certificate, StoreName.My, StoreLocation.CurrentUser, result.Diagnostics); + } + catch (Exception e) + { + result.Diagnostics.Error($"Error saving the certificate in the certificate store '{StoreLocation.CurrentUser}\\{StoreName.My}'.", e); + result.ResultCode = EnsureCertificateResult.ErrorSavingTheCertificateIntoTheCurrentUserPersonalStore; + return result; + } + } + if (path != null) + { + result.Diagnostics.Debug("Trying to export the certificate."); + result.Diagnostics.Debug(result.Diagnostics.DescribeCertificates(certificate)); + try + { + ExportCertificate(certificate, path, includePrivateKey, password, result.Diagnostics); + } + catch (Exception e) + { + result.Diagnostics.Error("An error ocurred exporting the certificate.", e); + result.ResultCode = EnsureCertificateResult.ErrorExportingTheCertificate; + return result; + } + } + + if ((RuntimeInformation.IsOSPlatform(OSPlatform.Windows) || RuntimeInformation.IsOSPlatform(OSPlatform.OSX)) && trust) + { + try + { + result.Diagnostics.Debug("Trying to export the certificate."); + TrustCertificate(certificate, result.Diagnostics); + } + catch (UserCancelledTrustException) + { + result.Diagnostics.Error("The user cancelled trusting the certificate.", null); + result.ResultCode = EnsureCertificateResult.UserCancelledTrustStep; + return result; + } + catch (Exception e) + { + result.Diagnostics.Error("There was an error trusting the certificate.", e); + result.ResultCode = EnsureCertificateResult.FailedToTrustTheCertificate; + return result; + } + } + + return result; + } + private class UserCancelledTrustException : Exception { } @@ -715,6 +887,65 @@ namespace Microsoft.AspNetCore.Certificates.Generation Trusted, All } + + internal class DetailedEnsureCertificateResult + { + public EnsureCertificateResult ResultCode { get; set; } + public DiagnosticInformation Diagnostics { get; set; } = new DiagnosticInformation(); + } #endif + + internal class DiagnosticInformation + { + public IList Messages { get; } = new List(); + + public IList Exceptions { get; } = new List(); + + internal void Debug(params string[] messages) + { + foreach (var message in messages) + { + Messages.Add(message); + } + } + + internal string[] DescribeCertificates(params X509Certificate2[] certificates) + { + return DescribeCertificates(certificates.AsEnumerable()); + } + + internal string[] DescribeCertificates(IEnumerable certificates) + { + var result = new List(); + result.Add($"'{certificates.Count()}' found matching the criteria."); + result.Add($"SUBJECT - THUMBPRINT - NOT BEFORE - EXPIRES - HAS PRIVATE KEY"); + foreach (var certificate in certificates) + { + result.Add(DescribeCertificate(certificate)); + } + + return result.ToArray(); + } + + private static string DescribeCertificate(X509Certificate2 certificate) => + $"{certificate.Subject} - {certificate.Thumbprint} - {certificate.NotBefore} - {certificate.NotAfter} - {certificate.HasPrivateKey}"; + + internal void Error(string preamble, Exception e) + { + Messages.Add(preamble); + if (Exceptions.Count > 0 && Exceptions[Exceptions.Count - 1] == e) + { + return; + } + + var ex = e; + while (ex != null) + { + Messages.Add("Exception message: " + ex.Message); + ex = ex.InnerException; + } + + } + } } } \ No newline at end of file diff --git a/src/Shared/CertificateGeneration/CertificatePurpose.cs b/src/Shared/CertificateGeneration/CertificatePurpose.cs index 1ad1a6d79b..7b3231f80d 100644 --- a/src/Shared/CertificateGeneration/CertificatePurpose.cs +++ b/src/Shared/CertificateGeneration/CertificatePurpose.cs @@ -6,7 +6,6 @@ namespace Microsoft.AspNetCore.Certificates.Generation internal enum CertificatePurpose { All, - HTTPS, - Signing + HTTPS } } \ No newline at end of file diff --git a/src/Shared/CertificateGeneration/EnsureCertificateResult.cs b/src/Shared/CertificateGeneration/EnsureCertificateResult.cs index d3c86ce05d..84c495249d 100644 --- a/src/Shared/CertificateGeneration/EnsureCertificateResult.cs +++ b/src/Shared/CertificateGeneration/EnsureCertificateResult.cs @@ -1,7 +1,7 @@ // 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. -#if NETCOREAPP2_0 || NETCOREAPP2_1 +#if NETCOREAPP2_0 || NETCOREAPP2_1 || NETCOREAPP2_2 namespace Microsoft.AspNetCore.Certificates.Generation { diff --git a/src/Shared/NonCapturingTimer/NonCapturingTimer.cs b/src/Shared/NonCapturingTimer/NonCapturingTimer.cs new file mode 100644 index 0000000000..6f54b2db47 --- /dev/null +++ b/src/Shared/NonCapturingTimer/NonCapturingTimer.cs @@ -0,0 +1,43 @@ +// 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.Threading; + +namespace Microsoft.Extensions.Internal +{ + // A convenience API for interacting with System.Threading.Timer in a way + // that doesn't capture the ExecutionContext. We should be using this (or equivalent) + // everywhere we use timers to avoid rooting any values stored in asynclocals. + internal static class NonCapturingTimer + { + public static Timer Create(TimerCallback callback, object state, TimeSpan dueTime, TimeSpan period) + { + if (callback == null) + { + throw new ArgumentNullException(nameof(callback)); + } + + // Don't capture the current ExecutionContext and its AsyncLocals onto the timer + bool restoreFlow = false; + try + { + if (!ExecutionContext.IsFlowSuppressed()) + { + ExecutionContext.SuppressFlow(); + restoreFlow = true; + } + + return new Timer(callback, state, dueTime, period); + } + finally + { + // Restore the current ExecutionContext + if (restoreFlow) + { + ExecutionContext.RestoreFlow(); + } + } + } + } +} diff --git a/src/Shared/Process/ProcessHelper.cs b/src/Shared/Process/ProcessHelper.cs index cf42a7e3a7..c6cbd1f970 100644 --- a/src/Shared/Process/ProcessHelper.cs +++ b/src/Shared/Process/ProcessHelper.cs @@ -14,44 +14,40 @@ namespace Microsoft.Extensions.Internal private static readonly bool _isWindows = RuntimeInformation.IsOSPlatform(OSPlatform.Windows); private static readonly TimeSpan _defaultTimeout = TimeSpan.FromSeconds(30); - public static void KillTree(this Process process) - { - process.KillTree(_defaultTimeout); - } + public static void KillTree(this Process process) => process.KillTree(_defaultTimeout); public static void KillTree(this Process process, TimeSpan timeout) { - string stdout; + var pid = process.Id; if (_isWindows) { RunProcessAndWaitForExit( "taskkill", - $"/T /F /PID {process.Id}", + $"/T /F /PID {pid}", timeout, - out stdout); + out var _); } else { var children = new HashSet(); - GetAllChildIdsUnix(process.Id, children, timeout); + GetAllChildIdsUnix(pid, children, timeout); foreach (var childId in children) { KillProcessUnix(childId, timeout); } - KillProcessUnix(process.Id, timeout); + KillProcessUnix(pid, timeout); } } private static void GetAllChildIdsUnix(int parentId, ISet children, TimeSpan timeout) { - string stdout; - var exitCode = RunProcessAndWaitForExit( + RunProcessAndWaitForExit( "pgrep", $"-P {parentId}", timeout, - out stdout); + out var stdout); - if (exitCode == 0 && !string.IsNullOrEmpty(stdout)) + if (!string.IsNullOrEmpty(stdout)) { using (var reader = new StringReader(stdout)) { @@ -63,8 +59,7 @@ namespace Microsoft.Extensions.Internal return; } - int id; - if (int.TryParse(text, out id)) + if (int.TryParse(text, out var id)) { children.Add(id); // Recursively get the children @@ -77,22 +72,22 @@ namespace Microsoft.Extensions.Internal private static void KillProcessUnix(int processId, TimeSpan timeout) { - string stdout; RunProcessAndWaitForExit( "kill", $"-TERM {processId}", timeout, - out stdout); + out var stdout); } - private static int RunProcessAndWaitForExit(string fileName, string arguments, TimeSpan timeout, out string stdout) + private static void RunProcessAndWaitForExit(string fileName, string arguments, TimeSpan timeout, out string stdout) { var startInfo = new ProcessStartInfo { FileName = fileName, Arguments = arguments, RedirectStandardOutput = true, - UseShellExecute = false + RedirectStandardError = true, + UseShellExecute = false, }; var process = Process.Start(startInfo); @@ -106,8 +101,6 @@ namespace Microsoft.Extensions.Internal { process.Kill(); } - - return process.ExitCode; } } } diff --git a/src/Shared/PropertyHelper/PropertyHelper.cs b/src/Shared/PropertyHelper/PropertyHelper.cs index 27ba5661a4..f6aad151e5 100644 --- a/src/Shared/PropertyHelper/PropertyHelper.cs +++ b/src/Shared/PropertyHelper/PropertyHelper.cs @@ -7,6 +7,7 @@ using System.Collections.Generic; using System.Diagnostics; using System.Linq; using System.Reflection; +using System.Runtime.CompilerServices; namespace Microsoft.Extensions.Internal { @@ -37,6 +38,12 @@ namespace Microsoft.Extensions.Internal private static readonly ConcurrentDictionary VisiblePropertiesCache = new ConcurrentDictionary(); + // We need to be able to check if a type is a 'ref struct' - but we need to be able to compile + // for platforms where the attribute is not defined, like net46. So we can fetch the attribute + // by late binding. If the attribute isn't defined, then we assume we won't encounter any + // 'ref struct' types. + private static readonly Type IsByRefLikeAttribute = Type.GetType("System.Runtime.CompilerServices.IsByRefLikeAttribute", throwOnError: false); + private Action _valueSetter; private Func _valueGetter; @@ -511,16 +518,34 @@ namespace Microsoft.Extensions.Internal return helpers; } - // Indexed properties are not useful (or valid) for grabbing properties off an object. + private static bool IsInterestingProperty(PropertyInfo property) { // For improving application startup time, do not use GetIndexParameters() api early in this check as it // creates a copy of parameter array and also we would like to check for the presence of a get method // and short circuit asap. - return property.GetMethod != null && + return + property.GetMethod != null && property.GetMethod.IsPublic && !property.GetMethod.IsStatic && + + // PropertyHelper can't work with ref structs. + !IsRefStructProperty(property) && + + // Indexed properties are not useful (or valid) for grabbing properties off an object. property.GetMethod.GetParameters().Length == 0; } + + // PropertyHelper can't really interact with ref-struct properties since they can't be + // boxed and can't be used as generic types. We just ignore them. + // + // see: https://github.com/aspnet/Mvc/issues/8545 + private static bool IsRefStructProperty(PropertyInfo property) + { + return + IsByRefLikeAttribute != null && + property.PropertyType.IsValueType && + property.PropertyType.IsDefined(IsByRefLikeAttribute); + } } } diff --git a/src/Shared/test/Shared.Tests/CertificateManagerTests.cs b/src/Shared/test/Shared.Tests/CertificateManagerTests.cs index 613f5c966f..cd314383c9 100644 --- a/src/Shared/test/Shared.Tests/CertificateManagerTests.cs +++ b/src/Shared/test/Shared.Tests/CertificateManagerTests.cs @@ -1,7 +1,7 @@ -// Copyright (c) .NET Foundation. All rights reserved. +// 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. -#if NETCOREAPP2_0 || NETCOREAPP2_1 +#if NETCOREAPP2_2 using System; using System.IO; @@ -26,7 +26,7 @@ namespace Microsoft.AspNetCore.Certificates.Generation.Tests public ITestOutputHelper Output { get; } - [Fact] + [Fact(Skip = "True")] public void EnsureCreateHttpsCertificate_CreatesACertificate_WhenThereAreNoHttpsCertificates() { try @@ -109,6 +109,92 @@ namespace Microsoft.AspNetCore.Certificates.Generation.Tests } } + [Fact] + public void EnsureCreateHttpsCertificate2_CreatesACertificate_WhenThereAreNoHttpsCertificates() + { + try + { + // Arrange + const string CertificateName = nameof(EnsureCreateHttpsCertificate_CreatesACertificate_WhenThereAreNoHttpsCertificates) + ".cer"; + var manager = new CertificateManager(); + manager.RemoveAllCertificates(CertificatePurpose.HTTPS, StoreName.My, StoreLocation.CurrentUser, TestCertificateSubject); + if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) + { + manager.RemoveAllCertificates(CertificatePurpose.HTTPS, StoreName.Root, StoreLocation.CurrentUser, TestCertificateSubject); + } + + // Act + DateTimeOffset now = DateTimeOffset.UtcNow; + now = new DateTimeOffset(now.Year, now.Month, now.Day, now.Hour, now.Minute, now.Second, 0, now.Offset); + var result = manager.EnsureAspNetCoreHttpsDevelopmentCertificate2(now, now.AddYears(1), CertificateName, trust: false, subject: TestCertificateSubject); + + // Assert + Assert.Equal(EnsureCertificateResult.Succeeded, result.ResultCode); + Assert.NotNull(result.Diagnostics); + Assert.NotEmpty(result.Diagnostics.Messages); + Assert.Empty(result.Diagnostics.Exceptions); + + Assert.True(File.Exists(CertificateName)); + + var exportedCertificate = new X509Certificate2(File.ReadAllBytes(CertificateName)); + Assert.NotNull(exportedCertificate); + Assert.False(exportedCertificate.HasPrivateKey); + + var httpsCertificates = manager.ListCertificates(CertificatePurpose.HTTPS, StoreName.My, StoreLocation.CurrentUser, isValid: false); + var httpsCertificate = Assert.Single(httpsCertificates, c => c.Subject == TestCertificateSubject); + Assert.True(httpsCertificate.HasPrivateKey); + Assert.Equal(TestCertificateSubject, httpsCertificate.Subject); + Assert.Equal(TestCertificateSubject, httpsCertificate.Issuer); + Assert.Equal("sha256RSA", httpsCertificate.SignatureAlgorithm.FriendlyName); + Assert.Equal("1.2.840.113549.1.1.11", httpsCertificate.SignatureAlgorithm.Value); + + Assert.Equal(now.LocalDateTime, httpsCertificate.NotBefore); + Assert.Equal(now.AddYears(1).LocalDateTime, httpsCertificate.NotAfter); + Assert.Contains( + httpsCertificate.Extensions.OfType(), + e => e is X509BasicConstraintsExtension basicConstraints && + basicConstraints.Critical == true && + basicConstraints.CertificateAuthority == false && + basicConstraints.HasPathLengthConstraint == false && + basicConstraints.PathLengthConstraint == 0); + + Assert.Contains( + httpsCertificate.Extensions.OfType(), + e => e is X509KeyUsageExtension keyUsage && + keyUsage.Critical == true && + keyUsage.KeyUsages == X509KeyUsageFlags.KeyEncipherment); + + Assert.Contains( + httpsCertificate.Extensions.OfType(), + e => e is X509EnhancedKeyUsageExtension enhancedKeyUsage && + enhancedKeyUsage.Critical == true && + enhancedKeyUsage.EnhancedKeyUsages.OfType().Single() is Oid keyUsage && + keyUsage.Value == "1.3.6.1.5.5.7.3.1"); + + // Subject alternative name + Assert.Contains( + httpsCertificate.Extensions.OfType(), + e => e.Critical == true && + e.Oid.Value == "2.5.29.17"); + + // ASP.NET HTTPS Development certificate extension + Assert.Contains( + httpsCertificate.Extensions.OfType(), + e => e.Critical == false && + e.Oid.Value == "1.3.6.1.4.1.311.84.1.1" && + Encoding.ASCII.GetString(e.RawData) == "ASP.NET Core HTTPS development certificate"); + + Assert.Equal(httpsCertificate.GetCertHashString(), exportedCertificate.GetCertHashString()); + + } + catch (Exception e) + { + Output.WriteLine(e.Message); + ListCertificates(Output); + throw; + } + } + private void ListCertificates(ITestOutputHelper output) { using (var store = new X509Store(StoreName.My, StoreLocation.CurrentUser)) @@ -125,7 +211,7 @@ namespace Microsoft.AspNetCore.Certificates.Generation.Tests } } - [Fact] + [Fact(Skip = "true")] public void EnsureCreateHttpsCertificate_DoesNotCreateACertificate_WhenThereIsAnExistingHttpsCertificates() { // Arrange @@ -196,108 +282,6 @@ namespace Microsoft.AspNetCore.Certificates.Generation.Tests Assert.Empty(manager.ListCertificates(CertificatePurpose.HTTPS, StoreName.Root, StoreLocation.CurrentUser, isValid: false).Where(c => c.Subject == TestCertificateSubject)); } } - - [Fact] - public void EnsureCreateIdentityTokenSigningCertificate_CreatesACertificate_WhenThereAreNoHttpsCertificates() - { - // Arrange - const string CertificateName = nameof(EnsureCreateIdentityTokenSigningCertificate_CreatesACertificate_WhenThereAreNoHttpsCertificates) + ".cer"; - var manager = new CertificateManager(); - - manager.RemoveAllCertificates(CertificatePurpose.Signing, StoreName.My, StoreLocation.CurrentUser, TestCertificateSubject); - if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) - { - manager.RemoveAllCertificates(CertificatePurpose.Signing, StoreName.Root, StoreLocation.CurrentUser, TestCertificateSubject); - } - - // Act - DateTimeOffset now = DateTimeOffset.UtcNow; - now = new DateTimeOffset(now.Year, now.Month, now.Day, now.Hour, now.Minute, now.Second, 0, now.Offset); - var result = manager.EnsureAspNetCoreApplicationTokensDevelopmentCertificate(now, now.AddYears(1), CertificateName, trust: false, subject: TestCertificateSubject); - - // Assert - Assert.Equal(EnsureCertificateResult.Succeeded, result); - Assert.True(File.Exists(CertificateName)); - - var exportedCertificate = new X509Certificate2(File.ReadAllBytes(CertificateName)); - Assert.NotNull(exportedCertificate); - Assert.False(exportedCertificate.HasPrivateKey); - - var identityCertificates = manager.ListCertificates(CertificatePurpose.Signing, StoreName.My, StoreLocation.CurrentUser, isValid: false); - var identityCertificate = Assert.Single(identityCertificates, i => i.Subject == TestCertificateSubject); - Assert.True(identityCertificate.HasPrivateKey); - Assert.Equal(TestCertificateSubject, identityCertificate.Subject); - Assert.Equal(TestCertificateSubject, identityCertificate.Issuer); - Assert.Equal("sha256RSA", identityCertificate.SignatureAlgorithm.FriendlyName); - Assert.Equal("1.2.840.113549.1.1.11", identityCertificate.SignatureAlgorithm.Value); - - Assert.Equal(now.LocalDateTime, identityCertificate.NotBefore); - Assert.Equal(now.AddYears(1).LocalDateTime, identityCertificate.NotAfter); - Assert.Contains( - identityCertificate.Extensions.OfType(), - e => e is X509BasicConstraintsExtension basicConstraints && - basicConstraints.Critical == true && - basicConstraints.CertificateAuthority == false && - basicConstraints.HasPathLengthConstraint == false && - basicConstraints.PathLengthConstraint == 0); - - Assert.Contains( - identityCertificate.Extensions.OfType(), - e => e is X509KeyUsageExtension keyUsage && - keyUsage.Critical == true && - keyUsage.KeyUsages == X509KeyUsageFlags.DigitalSignature); - - Assert.Contains( - identityCertificate.Extensions.OfType(), - e => e is X509EnhancedKeyUsageExtension enhancedKeyUsage && - enhancedKeyUsage.Critical == true && - enhancedKeyUsage.EnhancedKeyUsages.OfType().Single() is Oid keyUsage && - keyUsage.Value == "1.3.6.1.5.5.7.3.1"); - - // ASP.NET Core Identity Json Web Token signing development certificate - Assert.Contains( - identityCertificate.Extensions.OfType(), - e => e.Critical == false && - e.Oid.Value == "1.3.6.1.4.1.311.84.1.2" && - Encoding.ASCII.GetString(e.RawData) == "ASP.NET Core Identity Json Web Token signing development certificate"); - - Assert.Equal(identityCertificate.GetCertHashString(), exportedCertificate.GetCertHashString()); - } - - [Fact] - public void EnsureCreateIdentityTokenSigningCertificate_DoesNotCreateACertificate_WhenThereIsAnExistingHttpsCertificates() - { - // Arrange - const string CertificateName = nameof(EnsureCreateIdentityTokenSigningCertificate_DoesNotCreateACertificate_WhenThereIsAnExistingHttpsCertificates) + ".pfx"; - var certificatePassword = Guid.NewGuid().ToString(); - - var manager = new CertificateManager(); - - manager.RemoveAllCertificates(CertificatePurpose.Signing, StoreName.My, StoreLocation.CurrentUser, TestCertificateSubject); - if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) - { - manager.RemoveAllCertificates(CertificatePurpose.Signing, StoreName.Root, StoreLocation.CurrentUser, TestCertificateSubject); - } - - DateTimeOffset now = DateTimeOffset.UtcNow; - now = new DateTimeOffset(now.Year, now.Month, now.Day, now.Hour, now.Minute, now.Second, 0, now.Offset); - manager.EnsureAspNetCoreApplicationTokensDevelopmentCertificate(now, now.AddYears(1), path: null, trust: false, subject: TestCertificateSubject); - - var identityTokenSigningCertificates = manager.ListCertificates(CertificatePurpose.Signing, StoreName.My, StoreLocation.CurrentUser, isValid: false).Single(c => c.Subject == TestCertificateSubject); - - // Act - var result = manager.EnsureAspNetCoreApplicationTokensDevelopmentCertificate(now, now.AddYears(1), CertificateName, trust: false, includePrivateKey: true, password: certificatePassword, subject: TestCertificateSubject); - - // Assert - Assert.Equal(EnsureCertificateResult.ValidCertificatePresent, result); - Assert.True(File.Exists(CertificateName)); - - var exportedCertificate = new X509Certificate2(File.ReadAllBytes(CertificateName), certificatePassword); - Assert.NotNull(exportedCertificate); - Assert.True(exportedCertificate.HasPrivateKey); - - Assert.Equal(identityTokenSigningCertificates.GetCertHashString(), exportedCertificate.GetCertHashString()); - } } } diff --git a/src/Shared/test/Shared.Tests/DotNetMuxerTests.cs b/src/Shared/test/Shared.Tests/DotNetMuxerTests.cs index ba1dd06511..92e06a8f70 100644 --- a/src/Shared/test/Shared.Tests/DotNetMuxerTests.cs +++ b/src/Shared/test/Shared.Tests/DotNetMuxerTests.cs @@ -1,7 +1,7 @@ // 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. -#if NETCOREAPP2_0 || NETCOREAPP2_1 +#if NETCOREAPP2_2 using System.IO; using System.Runtime.InteropServices; using Xunit; diff --git a/src/Shared/test/Shared.Tests/NonCapturingTimerTest.cs b/src/Shared/test/Shared.Tests/NonCapturingTimerTest.cs new file mode 100644 index 0000000000..ef21ce5f3b --- /dev/null +++ b/src/Shared/test/Shared.Tests/NonCapturingTimerTest.cs @@ -0,0 +1,40 @@ +// 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.Threading; +using System.Threading.Tasks; +using Xunit; + +namespace Microsoft.Extensions.Internal +{ + public class NonCapturingTimerTest + { + [Fact] + public async Task NonCapturingTimer_DoesntCaptureExecutionContext() + { + // Arrange + var message = new AsyncLocal(); + message.Value = "Hey, this is a value stored in the execuion context"; + + var tcs = new TaskCompletionSource(); + + // Act + var timer = NonCapturingTimer.Create((_) => + { + // Observe the value based on the current execution context + tcs.SetResult(message.Value); + }, state: null, dueTime: TimeSpan.FromMilliseconds(1), Timeout.InfiniteTimeSpan); + + // Assert + var messageFromTimer = await tcs.Task; + timer.Dispose(); + + // ExecutionContext didn't flow to timer callback + Assert.Null(messageFromTimer); + + // ExecutionContext was restored + Assert.NotNull(await Task.Run(() => message.Value)); + } + } +} diff --git a/src/Shared/test/Shared.Tests/PropertyHelperTest.cs b/src/Shared/test/Shared.Tests/PropertyHelperTest.cs index 19cf08b370..1c43dc880b 100644 --- a/src/Shared/test/Shared.Tests/PropertyHelperTest.cs +++ b/src/Shared/test/Shared.Tests/PropertyHelperTest.cs @@ -153,6 +153,22 @@ namespace Microsoft.Extensions.Internal Assert.Equal("Prop5", helper.Name); } +#if NETSTANDARD || NETCOREAPP + [Fact] + public void PropertyHelper_RefStructProperties() + { + // Arrange + var obj = new RefStructProperties(); + + // Act + Assert + var helper = Assert.Single(PropertyHelper.GetProperties(obj.GetType().GetTypeInfo())); + Assert.Equal("Prop5", helper.Name); + } +#elif NET46 || NET461 +#else +#error Unknown TFM - update the set of TFMs where we test for ref structs +#endif + [Fact] public void PropertyHelper_DoesNotFindSetOnlyProperties() { @@ -718,6 +734,22 @@ namespace Microsoft.Extensions.Internal public int Prop5 { get; set; } } +#if NETSTANDARD || NETCOREAPP + private class RefStructProperties + { + public Span Span => throw new NotImplementedException(); + public MyRefStruct UserDefined => throw new NotImplementedException(); + + public int Prop5 { get; set; } + } + + private readonly ref struct MyRefStruct + { + } +#elif NET46 || NET461 +#else +#error Unknown TFM - update the set of TFMs where we test for ref structs +#endif private struct MyProperties { public int IntProp { get; set; } diff --git a/src/Shared/test/testassets/ThrowingLibrary/ThrowingLibrary.csproj b/src/Shared/test/testassets/ThrowingLibrary/ThrowingLibrary.csproj index d77d392873..2b2900911a 100644 --- a/src/Shared/test/testassets/ThrowingLibrary/ThrowingLibrary.csproj +++ b/src/Shared/test/testassets/ThrowingLibrary/ThrowingLibrary.csproj @@ -3,6 +3,7 @@ netstandard2.0 portable + false diff --git a/src/Testing/src/CultureReplacer.cs b/src/Testing/src/CultureReplacer.cs new file mode 100644 index 0000000000..51e35e8354 --- /dev/null +++ b/src/Testing/src/CultureReplacer.cs @@ -0,0 +1,79 @@ +// 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.Globalization; +using System.Threading; +using Xunit; + +namespace Microsoft.AspNetCore.Testing +{ + public class CultureReplacer : IDisposable + { + private const string _defaultCultureName = "en-GB"; + private const string _defaultUICultureName = "en-US"; + private static readonly CultureInfo _defaultCulture = new CultureInfo(_defaultCultureName); + private readonly CultureInfo _originalCulture; + private readonly CultureInfo _originalUICulture; + private readonly long _threadId; + + // Culture => Formatting of dates/times/money/etc, defaults to en-GB because en-US is the same as InvariantCulture + // We want to be able to find issues where the InvariantCulture is used, but a specific culture should be. + // + // UICulture => Language + public CultureReplacer(string culture = _defaultCultureName, string uiCulture = _defaultUICultureName) + : this(new CultureInfo(culture), new CultureInfo(uiCulture)) + { + } + + public CultureReplacer(CultureInfo culture, CultureInfo uiCulture) + { + _originalCulture = CultureInfo.CurrentCulture; + _originalUICulture = CultureInfo.CurrentUICulture; + _threadId = Thread.CurrentThread.ManagedThreadId; + CultureInfo.CurrentCulture = culture; + CultureInfo.CurrentUICulture = uiCulture; + } + + /// + /// The name of the culture that is used as the default value for CultureInfo.DefaultThreadCurrentCulture when CultureReplacer is used. + /// + public static string DefaultCultureName + { + get { return _defaultCultureName; } + } + + /// + /// The name of the culture that is used as the default value for [Thread.CurrentThread(NET45)/CultureInfo(K10)].CurrentUICulture when CultureReplacer is used. + /// + public static string DefaultUICultureName + { + get { return _defaultUICultureName; } + } + + /// + /// The culture that is used as the default value for [Thread.CurrentThread(NET45)/CultureInfo(K10)].CurrentCulture when CultureReplacer is used. + /// + public static CultureInfo DefaultCulture + { + get { return _defaultCulture; } + } + + public void Dispose() + { + Dispose(true); + GC.SuppressFinalize(this); + } + + private void Dispose(bool disposing) + { + if (disposing) + { + Assert.True(Thread.CurrentThread.ManagedThreadId == _threadId, + "The current thread is not the same as the thread invoking the constructor. This should never happen."); + CultureInfo.CurrentCulture = _originalCulture; + CultureInfo.CurrentUICulture = _originalUICulture; + } + } + } +} diff --git a/src/Testing/src/ExceptionAssertions.cs b/src/Testing/src/ExceptionAssertions.cs new file mode 100644 index 0000000000..244cad5a37 --- /dev/null +++ b/src/Testing/src/ExceptionAssertions.cs @@ -0,0 +1,271 @@ +// 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.Reflection; +using System.Threading.Tasks; +using Xunit; + +namespace Microsoft.AspNetCore.Testing +{ + // TODO: eventually want: public partial class Assert : Xunit.Assert + public static class ExceptionAssert + { + /// + /// Verifies that an exception of the given type (or optionally a derived type) is thrown. + /// + /// The type of the exception expected to be thrown + /// A delegate to the code to be tested + /// The exception that was thrown, when successful + public static TException Throws(Action testCode) + where TException : Exception + { + return VerifyException(RecordException(testCode)); + } + + /// + /// Verifies that an exception of the given type is thrown. + /// Also verifies that the exception message matches. + /// + /// The type of the exception expected to be thrown + /// A delegate to the code to be tested + /// The exception message to verify + /// The exception that was thrown, when successful + public static TException Throws(Action testCode, string exceptionMessage) + where TException : Exception + { + var ex = Throws(testCode); + VerifyExceptionMessage(ex, exceptionMessage); + return ex; + } + + /// + /// Verifies that an exception of the given type is thrown. + /// Also verifies that the exception message matches. + /// + /// The type of the exception expected to be thrown + /// A delegate to the code to be tested + /// The exception message to verify + /// The exception that was thrown, when successful + public static async Task ThrowsAsync(Func testCode, string exceptionMessage) + where TException : Exception + { + // The 'testCode' Task might execute asynchronously in a different thread making it hard to enforce the thread culture. + // The correct way to verify exception messages in such a scenario would be to run the task synchronously inside of a + // culture enforced block. + var ex = await Assert.ThrowsAsync(testCode); + VerifyExceptionMessage(ex, exceptionMessage); + return ex; + } + + /// + /// Verifies that an exception of the given type is thrown. + /// Also verified that the exception message matches. + /// + /// The type of the exception expected to be thrown + /// A delegate to the code to be tested + /// The exception message to verify + /// The exception that was thrown, when successful + public static TException Throws(Func testCode, string exceptionMessage) + where TException : Exception + { + return Throws(() => { testCode(); }, exceptionMessage); + } + + /// + /// Verifies that the code throws an . + /// + /// A delegate to the code to be tested + /// The name of the parameter that should throw the exception + /// The exception message to verify + /// The exception that was thrown, when successful + public static ArgumentException ThrowsArgument(Action testCode, string paramName, string exceptionMessage) + { + return ThrowsArgumentInternal(testCode, paramName, exceptionMessage); + } + + private static TException ThrowsArgumentInternal( + Action testCode, + string paramName, + string exceptionMessage) + where TException : ArgumentException + { + var ex = Throws(testCode); + if (paramName != null) + { + Assert.Equal(paramName, ex.ParamName); + } + VerifyExceptionMessage(ex, exceptionMessage, partialMatch: true); + return ex; + } + + /// + /// Verifies that the code throws an . + /// + /// A delegate to the code to be tested + /// The name of the parameter that should throw the exception + /// The exception message to verify + /// The exception that was thrown, when successful + public static Task ThrowsArgumentAsync(Func testCode, string paramName, string exceptionMessage) + { + return ThrowsArgumentAsyncInternal(testCode, paramName, exceptionMessage); + } + + private static async Task ThrowsArgumentAsyncInternal( + Func testCode, + string paramName, + string exceptionMessage) + where TException : ArgumentException + { + var ex = await Assert.ThrowsAsync(testCode); + if (paramName != null) + { + Assert.Equal(paramName, ex.ParamName); + } + VerifyExceptionMessage(ex, exceptionMessage, partialMatch: true); + return ex; + } + + /// + /// Verifies that the code throws an . + /// + /// A delegate to the code to be tested + /// The name of the parameter that should throw the exception + /// The exception that was thrown, when successful + public static ArgumentNullException ThrowsArgumentNull(Action testCode, string paramName) + { + var ex = Throws(testCode); + if (paramName != null) + { + Assert.Equal(paramName, ex.ParamName); + } + return ex; + } + + /// + /// Verifies that the code throws an ArgumentException with the expected message that indicates that the value cannot + /// be null or empty. + /// + /// A delegate to the code to be tested + /// The name of the parameter that should throw the exception + /// The exception that was thrown, when successful + public static ArgumentException ThrowsArgumentNullOrEmpty(Action testCode, string paramName) + { + return ThrowsArgumentInternal(testCode, paramName, "Value cannot be null or empty."); + } + + /// + /// Verifies that the code throws an ArgumentException with the expected message that indicates that the value cannot + /// be null or empty. + /// + /// A delegate to the code to be tested + /// The name of the parameter that should throw the exception + /// The exception that was thrown, when successful + public static Task ThrowsArgumentNullOrEmptyAsync(Func testCode, string paramName) + { + return ThrowsArgumentAsyncInternal(testCode, paramName, "Value cannot be null or empty."); + } + + /// + /// Verifies that the code throws an ArgumentNullException with the expected message that indicates that the value cannot + /// be null or empty string. + /// + /// A delegate to the code to be tested + /// The name of the parameter that should throw the exception + /// The exception that was thrown, when successful + public static ArgumentException ThrowsArgumentNullOrEmptyString(Action testCode, string paramName) + { + return ThrowsArgumentInternal(testCode, paramName, "Value cannot be null or an empty string."); + } + + /// + /// Verifies that the code throws an ArgumentNullException with the expected message that indicates that the value cannot + /// be null or empty string. + /// + /// A delegate to the code to be tested + /// The name of the parameter that should throw the exception + /// The exception that was thrown, when successful + public static Task ThrowsArgumentNullOrEmptyStringAsync(Func testCode, string paramName) + { + return ThrowsArgumentAsyncInternal(testCode, paramName, "Value cannot be null or an empty string."); + } + + /// + /// Verifies that the code throws an ArgumentOutOfRangeException (or optionally any exception which derives from it). + /// + /// A delegate to the code to be tested + /// The name of the parameter that should throw the exception + /// The exception message to verify + /// The actual value provided + /// The exception that was thrown, when successful + public static ArgumentOutOfRangeException ThrowsArgumentOutOfRange(Action testCode, string paramName, string exceptionMessage, object actualValue = null) + { + var ex = ThrowsArgumentInternal(testCode, paramName, exceptionMessage); + + if (paramName != null) + { + Assert.Equal(paramName, ex.ParamName); + } + + if (actualValue != null) + { + Assert.Equal(actualValue, ex.ActualValue); + } + + return ex; + } + + // We've re-implemented all the xUnit.net Throws code so that we can get this + // updated implementation of RecordException which silently unwraps any instances + // of AggregateException. In addition to unwrapping exceptions, this method ensures + // that tests are executed in with a known set of Culture and UICulture. This prevents + // tests from failing when executed on a non-English machine. + private static Exception RecordException(Action testCode) + { + try + { + using (new CultureReplacer()) + { + testCode(); + } + return null; + } + catch (Exception exception) + { + return UnwrapException(exception); + } + } + + private static Exception UnwrapException(Exception exception) + { + var aggEx = exception as AggregateException; + return aggEx != null ? aggEx.GetBaseException() : exception; + } + + private static TException VerifyException(Exception exception) + { + var tie = exception as TargetInvocationException; + if (tie != null) + { + exception = tie.InnerException; + } + Assert.NotNull(exception); + return Assert.IsAssignableFrom(exception); + } + + private static void VerifyExceptionMessage(Exception exception, string expectedMessage, bool partialMatch = false) + { + if (expectedMessage != null) + { + if (!partialMatch) + { + Assert.Equal(expectedMessage, exception.Message); + } + else + { + Assert.Contains(expectedMessage, exception.Message); + } + } + } + } +} \ No newline at end of file diff --git a/src/Testing/src/HttpClientSlim.cs b/src/Testing/src/HttpClientSlim.cs new file mode 100644 index 0000000000..6214ffefc1 --- /dev/null +++ b/src/Testing/src/HttpClientSlim.cs @@ -0,0 +1,158 @@ +// 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.Globalization; +using System.IO; +using System.Net; +using System.Net.Http; +using System.Net.Security; +using System.Net.Sockets; +using System.Security.Authentication; +using System.Text; +using System.Threading.Tasks; + +namespace Microsoft.AspNetCore.Testing +{ + /// + /// Lightweight version of HttpClient implemented using Socket and SslStream. + /// + public static class HttpClientSlim + { + public static async Task GetStringAsync(string requestUri, bool validateCertificate = true) + => await GetStringAsync(new Uri(requestUri), validateCertificate).ConfigureAwait(false); + + public static async Task GetStringAsync(Uri requestUri, bool validateCertificate = true) + { + using (var stream = await GetStream(requestUri, validateCertificate).ConfigureAwait(false)) + { + using (var writer = new StreamWriter(stream, Encoding.ASCII, bufferSize: 1024, leaveOpen: true)) + { + await writer.WriteAsync($"GET {requestUri.PathAndQuery} HTTP/1.0\r\n").ConfigureAwait(false); + await writer.WriteAsync($"Host: {GetHost(requestUri)}\r\n").ConfigureAwait(false); + await writer.WriteAsync("\r\n").ConfigureAwait(false); + } + + return await ReadResponse(stream).ConfigureAwait(false); + } + } + + internal static string GetHost(Uri requestUri) + { + var authority = requestUri.Authority; + if (requestUri.HostNameType == UriHostNameType.IPv6) + { + // Make sure there's no % scope id. https://github.com/aspnet/KestrelHttpServer/issues/2637 + var address = IPAddress.Parse(requestUri.Host); + address = new IPAddress(address.GetAddressBytes()); // Drop scope Id. + if (requestUri.IsDefaultPort) + { + authority = $"[{address}]"; + } + else + { + authority = $"[{address}]:{requestUri.Port.ToString(CultureInfo.InvariantCulture)}"; + } + } + return authority; + } + + public static async Task PostAsync(string requestUri, HttpContent content, bool validateCertificate = true) + => await PostAsync(new Uri(requestUri), content, validateCertificate).ConfigureAwait(false); + + public static async Task PostAsync(Uri requestUri, HttpContent content, bool validateCertificate = true) + { + using (var stream = await GetStream(requestUri, validateCertificate)) + { + using (var writer = new StreamWriter(stream, Encoding.ASCII, bufferSize: 1024, leaveOpen: true)) + { + await writer.WriteAsync($"POST {requestUri.PathAndQuery} HTTP/1.0\r\n").ConfigureAwait(false); + await writer.WriteAsync($"Host: {requestUri.Authority}\r\n").ConfigureAwait(false); + await writer.WriteAsync($"Content-Type: {content.Headers.ContentType}\r\n").ConfigureAwait(false); + await writer.WriteAsync($"Content-Length: {content.Headers.ContentLength}\r\n").ConfigureAwait(false); + await writer.WriteAsync("\r\n").ConfigureAwait(false); + } + + await content.CopyToAsync(stream).ConfigureAwait(false); + + return await ReadResponse(stream).ConfigureAwait(false); + } + } + + private static async Task ReadResponse(Stream stream) + { + using (var reader = new StreamReader(stream, Encoding.ASCII, detectEncodingFromByteOrderMarks: true, + bufferSize: 1024, leaveOpen: true)) + { + var response = await reader.ReadToEndAsync().ConfigureAwait(false); + + var status = GetStatus(response); + new HttpResponseMessage(status).EnsureSuccessStatusCode(); + + var body = response.Substring(response.IndexOf("\r\n\r\n") + 4); + return body; + } + } + + private static HttpStatusCode GetStatus(string response) + { + var statusStart = response.IndexOf(' ') + 1; + var statusEnd = response.IndexOf(' ', statusStart) - 1; + var statusLength = statusEnd - statusStart + 1; + + if (statusLength < 1) + { + throw new InvalidDataException($"No StatusCode found in '{response}'"); + } + + return (HttpStatusCode)int.Parse(response.Substring(statusStart, statusLength)); + } + + private static async Task GetStream(Uri requestUri, bool validateCertificate) + { + var socket = await GetSocket(requestUri); + var stream = new NetworkStream(socket, ownsSocket: true); + + if (requestUri.Scheme.Equals("https", StringComparison.OrdinalIgnoreCase)) + { + var sslStream = new SslStream(stream, leaveInnerStreamOpen: false, userCertificateValidationCallback: + validateCertificate ? null : (RemoteCertificateValidationCallback)((a, b, c, d) => true)); + + await sslStream.AuthenticateAsClientAsync(requestUri.Host, clientCertificates: null, + enabledSslProtocols: SslProtocols.Tls11 | SslProtocols.Tls12, + checkCertificateRevocation: validateCertificate).ConfigureAwait(false); + return sslStream; + } + else + { + return stream; + } + } + + public static async Task GetSocket(Uri requestUri) + { + var tcs = new TaskCompletionSource(); + + var socketArgs = new SocketAsyncEventArgs(); + socketArgs.RemoteEndPoint = new DnsEndPoint(requestUri.DnsSafeHost, requestUri.Port); + socketArgs.Completed += (s, e) => tcs.TrySetResult(e.ConnectSocket); + + // Must use static ConnectAsync(), since instance Connect() does not support DNS names on OSX/Linux. + if (Socket.ConnectAsync(SocketType.Stream, ProtocolType.Tcp, socketArgs)) + { + await tcs.Task.ConfigureAwait(false); + } + + var socket = socketArgs.ConnectSocket; + + if (socket == null) + { + throw new SocketException((int)socketArgs.SocketError); + } + else + { + return socket; + } + } + } +} diff --git a/src/Testing/src/Microsoft.AspNetCore.Testing.csproj b/src/Testing/src/Microsoft.AspNetCore.Testing.csproj new file mode 100644 index 0000000000..d9d9008dd2 --- /dev/null +++ b/src/Testing/src/Microsoft.AspNetCore.Testing.csproj @@ -0,0 +1,33 @@ + + + + Various helpers for writing tests that use ASP.NET Core. + netstandard2.0;net46 + $(NoWarn);CS1591 + true + aspnetcore + false + true + + + + + + + + + + + + + + + + + + True + contentFiles\cs\netstandard2.0\ + + + + diff --git a/src/Testing/src/Properties/AssemblyInfo.cs b/src/Testing/src/Properties/AssemblyInfo.cs new file mode 100644 index 0000000000..0212e111ee --- /dev/null +++ b/src/Testing/src/Properties/AssemblyInfo.cs @@ -0,0 +1,6 @@ +// 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.Runtime.CompilerServices; + +[assembly: InternalsVisibleTo("Microsoft.AspNetCore.Testing.Tests, PublicKey=0024000004800000940000000602000000240000525341310004000001000100f33a29044fa9d740c9b3213a93e57c84b472c84e0b8a0e1ae48e67a9f8f6de9d5f7f3d52ac23e48ac51801f1dc950abe901da34d2a9e3baadb141a17c77ef3c565dd5ee5054b91cf63bb3c6ab83f72ab3aafe93d0fc3c2348b764fafb0b1c0733de51459aeab46580384bf9d74c4e28164b7cde247f891ba07891c9d872ad2bb")] diff --git a/src/Testing/src/ReplaceCulture.cs b/src/Testing/src/ReplaceCulture.cs new file mode 100644 index 0000000000..9580bfd0da --- /dev/null +++ b/src/Testing/src/ReplaceCulture.cs @@ -0,0 +1,70 @@ +// 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.Globalization; +using System.Reflection; +using Xunit.Sdk; + +namespace Microsoft.AspNetCore.Testing +{ + /// + /// Replaces the current culture and UI culture for the test. + /// + [AttributeUsage(AttributeTargets.Method)] + public class ReplaceCultureAttribute : BeforeAfterTestAttribute + { + private const string _defaultCultureName = "en-GB"; + private const string _defaultUICultureName = "en-US"; + private CultureInfo _originalCulture; + private CultureInfo _originalUICulture; + + /// + /// Replaces the current culture and UI culture to en-GB and en-US respectively. + /// + public ReplaceCultureAttribute() : + this(_defaultCultureName, _defaultUICultureName) + { + } + + /// + /// Replaces the current culture and UI culture based on specified values. + /// + public ReplaceCultureAttribute(string currentCulture, string currentUICulture) + { + Culture = new CultureInfo(currentCulture); + UICulture = new CultureInfo(currentUICulture); + } + + /// + /// The for the test. Defaults to en-GB. + /// + /// + /// en-GB is used here as the default because en-US is equivalent to the InvariantCulture. We + /// want to be able to find bugs where we're accidentally relying on the Invariant instead of the + /// user's culture. + /// + public CultureInfo Culture { get; } + + /// + /// The for the test. Defaults to en-US. + /// + public CultureInfo UICulture { get; } + + public override void Before(MethodInfo methodUnderTest) + { + _originalCulture = CultureInfo.CurrentCulture; + _originalUICulture = CultureInfo.CurrentUICulture; + + CultureInfo.CurrentCulture = Culture; + CultureInfo.CurrentUICulture = UICulture; + } + + public override void After(MethodInfo methodUnderTest) + { + CultureInfo.CurrentCulture = _originalCulture; + CultureInfo.CurrentUICulture = _originalUICulture; + } + } +} + diff --git a/src/Testing/src/TaskExtensions.cs b/src/Testing/src/TaskExtensions.cs new file mode 100644 index 0000000000..83130aeae4 --- /dev/null +++ b/src/Testing/src/TaskExtensions.cs @@ -0,0 +1,64 @@ +// 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; +using System.Threading; +using System.Threading.Tasks; + +namespace Microsoft.AspNetCore.Testing +{ + public static class TaskExtensions + { + public static async Task TimeoutAfter(this Task task, TimeSpan timeout, + [CallerFilePath] string filePath = null, + [CallerLineNumber] int lineNumber = default(int)) + { + // Don't create a timer if the task is already completed + if (task.IsCompleted) + { + return await task; + } + + var cts = new CancellationTokenSource(); + if (task == await Task.WhenAny(task, Task.Delay(timeout, cts.Token))) + { + cts.Cancel(); + return await task; + } + else + { + throw new TimeoutException( + CreateMessage(timeout, filePath, lineNumber)); + } + } + + public static async Task TimeoutAfter(this Task task, TimeSpan timeout, + [CallerFilePath] string filePath = null, + [CallerLineNumber] int lineNumber = default(int)) + { + // Don't create a timer if the task is already completed + if (task.IsCompleted) + { + await task; + return; + } + + var cts = new CancellationTokenSource(); + if (task == await Task.WhenAny(task, Task.Delay(timeout, cts.Token))) + { + cts.Cancel(); + await task; + } + else + { + throw new TimeoutException(CreateMessage(timeout, filePath, lineNumber)); + } + } + + private static string CreateMessage(TimeSpan timeout, string filePath, int lineNumber) + => string.IsNullOrEmpty(filePath) + ? $"The operation timed out after reaching the limit of {timeout.TotalMilliseconds}ms." + : $"The operation at {filePath}:{lineNumber} timed out after reaching the limit of {timeout.TotalMilliseconds}ms."; + } +} diff --git a/src/Testing/src/TestPathUtilities.cs b/src/Testing/src/TestPathUtilities.cs new file mode 100644 index 0000000000..ebd10897c3 --- /dev/null +++ b/src/Testing/src/TestPathUtilities.cs @@ -0,0 +1,31 @@ +// 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.IO; + +namespace Microsoft.AspNetCore.Testing +{ + public class TestPathUtilities + { + public static string GetSolutionRootDirectory(string solution) + { + var applicationBasePath = AppContext.BaseDirectory; + var directoryInfo = new DirectoryInfo(applicationBasePath); + + do + { + var projectFileInfo = new FileInfo(Path.Combine(directoryInfo.FullName, $"{solution}.sln")); + if (projectFileInfo.Exists) + { + return projectFileInfo.DirectoryName; + } + + directoryInfo = directoryInfo.Parent; + } + while (directoryInfo.Parent != null); + + throw new Exception($"Solution file {solution}.sln could not be found in {applicationBasePath} or its parent directories."); + } + } +} diff --git a/src/Testing/src/TestPlatformHelper.cs b/src/Testing/src/TestPlatformHelper.cs new file mode 100644 index 0000000000..1a3f275c7e --- /dev/null +++ b/src/Testing/src/TestPlatformHelper.cs @@ -0,0 +1,23 @@ +// 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.InteropServices; + +namespace Microsoft.AspNetCore.Testing +{ + public static class TestPlatformHelper + { + public static bool IsMono => + Type.GetType("Mono.Runtime") != null; + + public static bool IsWindows => + RuntimeInformation.IsOSPlatform(OSPlatform.Windows); + + public static bool IsLinux => + RuntimeInformation.IsOSPlatform(OSPlatform.Linux); + + public static bool IsMac => + RuntimeInformation.IsOSPlatform(OSPlatform.OSX); + } +} \ No newline at end of file diff --git a/src/Testing/src/Tracing/CollectingEventListener.cs b/src/Testing/src/Tracing/CollectingEventListener.cs new file mode 100644 index 0000000000..d22a4996af --- /dev/null +++ b/src/Testing/src/Tracing/CollectingEventListener.cs @@ -0,0 +1,60 @@ +using System; +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.Diagnostics.Tracing; +using System.Linq; + +namespace Microsoft.AspNetCore.Testing.Tracing +{ + public class CollectingEventListener : EventListener + { + private ConcurrentQueue _events = new ConcurrentQueue(); + + private object _lock = new object(); + + private Dictionary _existingSources = new Dictionary(StringComparer.OrdinalIgnoreCase); + private HashSet _requestedEventSources = new HashSet(); + + public void CollectFrom(string eventSourceName) + { + lock(_lock) + { + // Check if it's already been created + if(_existingSources.TryGetValue(eventSourceName, out var existingSource)) + { + // It has, so just enable it now + CollectFrom(existingSource); + } + else + { + // It hasn't, so queue this request for when it is created + _requestedEventSources.Add(eventSourceName); + } + } + } + + public void CollectFrom(EventSource eventSource) => EnableEvents(eventSource, EventLevel.Verbose, EventKeywords.All); + + public IReadOnlyList GetEventsWritten() => _events.ToArray(); + + protected override void OnEventSourceCreated(EventSource eventSource) + { + lock (_lock) + { + // Add this to the list of existing sources for future CollectEventsFrom requests. + _existingSources[eventSource.Name] = eventSource; + + // Check if we have a pending request to enable it + if (_requestedEventSources.Contains(eventSource.Name)) + { + CollectFrom(eventSource); + } + } + } + + protected override void OnEventWritten(EventWrittenEventArgs eventData) + { + _events.Enqueue(eventData); + } + } +} diff --git a/src/Testing/src/Tracing/EventAssert.cs b/src/Testing/src/Tracing/EventAssert.cs new file mode 100644 index 0000000000..b32fb36dad --- /dev/null +++ b/src/Testing/src/Tracing/EventAssert.cs @@ -0,0 +1,60 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics.Tracing; +using System.Linq; +using Xunit; + +namespace Microsoft.AspNetCore.Testing.Tracing +{ + public class EventAssert + { + private readonly int _expectedId; + private readonly string _expectedName; + private readonly EventLevel _expectedLevel; + private readonly IList<(string name, Action asserter)> _payloadAsserters = new List<(string, Action)>(); + + public EventAssert(int expectedId, string expectedName, EventLevel expectedLevel) + { + _expectedId = expectedId; + _expectedName = expectedName; + _expectedLevel = expectedLevel; + } + + public static void Collection(IEnumerable events, params EventAssert[] asserts) + { + Assert.Collection( + events, + asserts.Select(a => a.CreateAsserter()).ToArray()); + } + + public static EventAssert Event(int id, string name, EventLevel level) + { + return new EventAssert(id, name, level); + } + + public EventAssert Payload(string name, object expectedValue) => Payload(name, actualValue => Assert.Equal(expectedValue, actualValue)); + + public EventAssert Payload(string name, Action asserter) + { + _payloadAsserters.Add((name, asserter)); + return this; + } + + private Action CreateAsserter() => Execute; + + private void Execute(EventWrittenEventArgs evt) + { + Assert.Equal(_expectedId, evt.EventId); + Assert.Equal(_expectedName, evt.EventName); + Assert.Equal(_expectedLevel, evt.Level); + + Action CreateNameAsserter((string name, Action asserter) val) + { + return actualValue => Assert.Equal(val.name, actualValue); + } + + Assert.Collection(evt.PayloadNames, _payloadAsserters.Select(CreateNameAsserter).ToArray()); + Assert.Collection(evt.Payload, _payloadAsserters.Select(t => t.asserter).ToArray()); + } + } +} diff --git a/src/Testing/src/Tracing/EventSourceTestBase.cs b/src/Testing/src/Tracing/EventSourceTestBase.cs new file mode 100644 index 0000000000..721966d6c5 --- /dev/null +++ b/src/Testing/src/Tracing/EventSourceTestBase.cs @@ -0,0 +1,39 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics.Tracing; +using Xunit; + +namespace Microsoft.AspNetCore.Testing.Tracing +{ + // This collection attribute is what makes the "magic" happen. It forces xunit to run all tests that inherit from this + // base class sequentially, preventing conflicts (since EventSource/EventListener is a process-global concept). + [Collection(CollectionName)] + public abstract class EventSourceTestBase : IDisposable + { + public const string CollectionName = "Microsoft.AspNetCore.Testing.Tracing.EventSourceTestCollection"; + + private readonly CollectingEventListener _listener; + + public EventSourceTestBase() + { + _listener = new CollectingEventListener(); + } + + protected void CollectFrom(string eventSourceName) + { + _listener.CollectFrom(eventSourceName); + } + + protected void CollectFrom(EventSource eventSource) + { + _listener.CollectFrom(eventSource); + } + + protected IReadOnlyList GetEvents() => _listener.GetEventsWritten(); + + public void Dispose() + { + _listener.Dispose(); + } + } +} diff --git a/src/Testing/src/contentFiles/cs/netstandard2.0/EventSourceTestCollection.cs b/src/Testing/src/contentFiles/cs/netstandard2.0/EventSourceTestCollection.cs new file mode 100644 index 0000000000..0ed9e1a9a9 --- /dev/null +++ b/src/Testing/src/contentFiles/cs/netstandard2.0/EventSourceTestCollection.cs @@ -0,0 +1,10 @@ +namespace Microsoft.AspNetCore.Testing.Tracing +{ + // This file comes from Microsoft.AspNetCore.Testing and has to be defined in the test assembly. + // It enables EventSourceTestBase's parallel isolation functionality. + + [Xunit.CollectionDefinition(EventSourceTestBase.CollectionName, DisableParallelization = true)] + public class EventSourceTestCollection + { + } +} diff --git a/src/Testing/src/xunit/ConditionalFactAttribute.cs b/src/Testing/src/xunit/ConditionalFactAttribute.cs new file mode 100644 index 0000000000..7448b48d8c --- /dev/null +++ b/src/Testing/src/xunit/ConditionalFactAttribute.cs @@ -0,0 +1,15 @@ +// 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 Xunit; +using Xunit.Sdk; + +namespace Microsoft.AspNetCore.Testing.xunit +{ + [AttributeUsage(AttributeTargets.Method, AllowMultiple = false)] + [XunitTestCaseDiscoverer("Microsoft.AspNetCore.Testing.xunit." + nameof(ConditionalFactDiscoverer), "Microsoft.AspNetCore.Testing")] + public class ConditionalFactAttribute : FactAttribute + { + } +} \ No newline at end of file diff --git a/src/Testing/src/xunit/ConditionalFactDiscoverer.cs b/src/Testing/src/xunit/ConditionalFactDiscoverer.cs new file mode 100644 index 0000000000..819373fa31 --- /dev/null +++ b/src/Testing/src/xunit/ConditionalFactDiscoverer.cs @@ -0,0 +1,27 @@ +// 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 Xunit.Abstractions; +using Xunit.Sdk; + +namespace Microsoft.AspNetCore.Testing.xunit +{ + internal class ConditionalFactDiscoverer : FactDiscoverer + { + private readonly IMessageSink _diagnosticMessageSink; + + public ConditionalFactDiscoverer(IMessageSink diagnosticMessageSink) + : base(diagnosticMessageSink) + { + _diagnosticMessageSink = diagnosticMessageSink; + } + + protected override IXunitTestCase CreateTestCase(ITestFrameworkDiscoveryOptions discoveryOptions, ITestMethod testMethod, IAttributeInfo factAttribute) + { + var skipReason = testMethod.EvaluateSkipConditions(); + return skipReason != null + ? new SkippedTestCase(skipReason, _diagnosticMessageSink, discoveryOptions.MethodDisplayOrDefault(), testMethod) + : base.CreateTestCase(discoveryOptions, testMethod, factAttribute); + } + } +} \ No newline at end of file diff --git a/src/Testing/src/xunit/ConditionalTheoryAttribute.cs b/src/Testing/src/xunit/ConditionalTheoryAttribute.cs new file mode 100644 index 0000000000..9249078cc5 --- /dev/null +++ b/src/Testing/src/xunit/ConditionalTheoryAttribute.cs @@ -0,0 +1,15 @@ +// 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 Xunit; +using Xunit.Sdk; + +namespace Microsoft.AspNetCore.Testing.xunit +{ + [AttributeUsage(AttributeTargets.Method, AllowMultiple = false)] + [XunitTestCaseDiscoverer("Microsoft.AspNetCore.Testing.xunit." + nameof(ConditionalTheoryDiscoverer), "Microsoft.AspNetCore.Testing")] + public class ConditionalTheoryAttribute : TheoryAttribute + { + } +} \ No newline at end of file diff --git a/src/Testing/src/xunit/ConditionalTheoryDiscoverer.cs b/src/Testing/src/xunit/ConditionalTheoryDiscoverer.cs new file mode 100644 index 0000000000..d24421f5cd --- /dev/null +++ b/src/Testing/src/xunit/ConditionalTheoryDiscoverer.cs @@ -0,0 +1,47 @@ +// 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.Collections.Generic; +using Xunit.Abstractions; +using Xunit.Sdk; + +namespace Microsoft.AspNetCore.Testing.xunit +{ + internal class ConditionalTheoryDiscoverer : TheoryDiscoverer + { + public ConditionalTheoryDiscoverer(IMessageSink diagnosticMessageSink) + : base(diagnosticMessageSink) + { + } + + protected override IEnumerable CreateTestCasesForTheory(ITestFrameworkDiscoveryOptions discoveryOptions, ITestMethod testMethod, IAttributeInfo theoryAttribute) + { + var skipReason = testMethod.EvaluateSkipConditions(); + return skipReason != null + ? new[] { new SkippedTestCase(skipReason, DiagnosticMessageSink, discoveryOptions.MethodDisplayOrDefault(), testMethod) } + : base.CreateTestCasesForTheory(discoveryOptions, testMethod, theoryAttribute); + } + + protected override IEnumerable CreateTestCasesForDataRow(ITestFrameworkDiscoveryOptions discoveryOptions, ITestMethod testMethod, IAttributeInfo theoryAttribute, object[] dataRow) + { + var skipReason = testMethod.EvaluateSkipConditions(); + if (skipReason == null && dataRow?.Length > 0) + { + var obj = dataRow[0]; + if (obj != null) + { + var type = obj.GetType(); + var property = type.GetProperty("Skip"); + if (property != null && property.PropertyType.Equals(typeof(string))) + { + skipReason = property.GetValue(obj) as string; + } + } + } + + return skipReason != null ? + base.CreateTestCasesForSkippedDataRow(discoveryOptions, testMethod, theoryAttribute, dataRow, skipReason) + : base.CreateTestCasesForDataRow(discoveryOptions, testMethod, theoryAttribute, dataRow); + } + } +} \ No newline at end of file diff --git a/src/Testing/src/xunit/DockerOnlyAttribute.cs b/src/Testing/src/xunit/DockerOnlyAttribute.cs new file mode 100644 index 0000000000..d67a35a672 --- /dev/null +++ b/src/Testing/src/xunit/DockerOnlyAttribute.cs @@ -0,0 +1,38 @@ +// 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.IO; +using System.Linq; +using System.Runtime.InteropServices; + +namespace Microsoft.AspNetCore.Testing.xunit +{ + [AttributeUsage(AttributeTargets.Method, Inherited = true, AllowMultiple = false)] + public sealed class DockerOnlyAttribute : Attribute, ITestCondition + { + public string SkipReason { get; } = "This test can only run in a Docker container."; + + public bool IsMet + { + get + { + if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) + { + // we currently don't have a good way to detect if running in a Windows container + return false; + } + + const string procFile = "/proc/1/cgroup"; + if (!File.Exists(procFile)) + { + return false; + } + + var lines = File.ReadAllLines(procFile); + // typically the last line in the file is "1:name=openrc:/docker" + return lines.Reverse().Any(l => l.EndsWith("name=openrc:/docker", StringComparison.Ordinal)); + } + } + } +} diff --git a/src/Testing/src/xunit/EnvironmentVariableSkipConditionAttribute.cs b/src/Testing/src/xunit/EnvironmentVariableSkipConditionAttribute.cs new file mode 100644 index 0000000000..8bf1bfd15e --- /dev/null +++ b/src/Testing/src/xunit/EnvironmentVariableSkipConditionAttribute.cs @@ -0,0 +1,95 @@ +// 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.Linq; + +namespace Microsoft.AspNetCore.Testing.xunit +{ + /// + /// Skips a test when the value of an environment variable matches any of the supplied values. + /// + [AttributeUsage(AttributeTargets.Method | AttributeTargets.Class | AttributeTargets.Assembly, AllowMultiple = true)] + public class EnvironmentVariableSkipConditionAttribute : Attribute, ITestCondition + { + private readonly string _variableName; + private readonly string[] _values; + private string _currentValue; + private readonly IEnvironmentVariable _environmentVariable; + + /// + /// Creates a new instance of . + /// + /// Name of the environment variable. + /// Value(s) of the environment variable to match for the test to be skipped + public EnvironmentVariableSkipConditionAttribute(string variableName, params string[] values) + : this(new EnvironmentVariable(), variableName, values) + { + } + + // To enable unit testing + internal EnvironmentVariableSkipConditionAttribute( + IEnvironmentVariable environmentVariable, + string variableName, + params string[] values) + { + if (environmentVariable == null) + { + throw new ArgumentNullException(nameof(environmentVariable)); + } + if (variableName == null) + { + throw new ArgumentNullException(nameof(variableName)); + } + if (values == null) + { + throw new ArgumentNullException(nameof(values)); + } + + _variableName = variableName; + _values = values; + _environmentVariable = environmentVariable; + } + + /// + /// Skips the test only if the value of the variable matches any of the supplied values. Default is True. + /// + public bool SkipOnMatch { get; set; } = true; + + public bool IsMet + { + get + { + _currentValue = _environmentVariable.Get(_variableName); + var hasMatched = _values.Any(value => string.Compare(value, _currentValue, ignoreCase: true) == 0); + + if (SkipOnMatch) + { + return hasMatched; + } + else + { + return !hasMatched; + } + } + } + + public string SkipReason + { + get + { + var value = _currentValue == null ? "(null)" : _currentValue; + return $"Test skipped on environment variable with name '{_variableName}' and value '{value}' " + + $"for the '{nameof(SkipOnMatch)}' value of '{SkipOnMatch}'."; + } + } + + private struct EnvironmentVariable : IEnvironmentVariable + { + public string Get(string name) + { + return Environment.GetEnvironmentVariable(name); + } + } + } +} diff --git a/src/Testing/src/xunit/FrameworkSkipConditionAttribute.cs b/src/Testing/src/xunit/FrameworkSkipConditionAttribute.cs new file mode 100644 index 0000000000..168076a434 --- /dev/null +++ b/src/Testing/src/xunit/FrameworkSkipConditionAttribute.cs @@ -0,0 +1,57 @@ +// 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; + +namespace Microsoft.AspNetCore.Testing.xunit +{ + [AttributeUsage(AttributeTargets.Method, AllowMultiple = false)] + public class FrameworkSkipConditionAttribute : Attribute, ITestCondition + { + private readonly RuntimeFrameworks _excludedFrameworks; + + public FrameworkSkipConditionAttribute(RuntimeFrameworks excludedFrameworks) + { + _excludedFrameworks = excludedFrameworks; + } + + public bool IsMet + { + get + { + return CanRunOnThisFramework(_excludedFrameworks); + } + } + + public string SkipReason { get; set; } = "Test cannot run on this runtime framework."; + + private static bool CanRunOnThisFramework(RuntimeFrameworks excludedFrameworks) + { + if (excludedFrameworks == RuntimeFrameworks.None) + { + return true; + } + +#if NET461 || NET46 + if (excludedFrameworks.HasFlag(RuntimeFrameworks.Mono) && + TestPlatformHelper.IsMono) + { + return false; + } + + if (excludedFrameworks.HasFlag(RuntimeFrameworks.CLR)) + { + return false; + } +#elif NETSTANDARD2_0 + if (excludedFrameworks.HasFlag(RuntimeFrameworks.CoreCLR)) + { + return false; + } +#else +#error Target frameworks need to be updated. +#endif + return true; + } + } +} \ No newline at end of file diff --git a/src/Testing/src/xunit/IEnvironmentVariable.cs b/src/Testing/src/xunit/IEnvironmentVariable.cs new file mode 100644 index 0000000000..068c210611 --- /dev/null +++ b/src/Testing/src/xunit/IEnvironmentVariable.cs @@ -0,0 +1,10 @@ +// 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. + +namespace Microsoft.AspNetCore.Testing.xunit +{ + internal interface IEnvironmentVariable + { + string Get(string name); + } +} diff --git a/src/Testing/src/xunit/ITestCondition.cs b/src/Testing/src/xunit/ITestCondition.cs new file mode 100644 index 0000000000..bb6ff1f031 --- /dev/null +++ b/src/Testing/src/xunit/ITestCondition.cs @@ -0,0 +1,12 @@ +// 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. + +namespace Microsoft.AspNetCore.Testing.xunit +{ + public interface ITestCondition + { + bool IsMet { get; } + + string SkipReason { get; } + } +} \ No newline at end of file diff --git a/src/Testing/src/xunit/MinimumOsVersionAttribute.cs b/src/Testing/src/xunit/MinimumOsVersionAttribute.cs new file mode 100644 index 0000000000..89e3b19556 --- /dev/null +++ b/src/Testing/src/xunit/MinimumOsVersionAttribute.cs @@ -0,0 +1,111 @@ +// 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.InteropServices; +using Microsoft.Win32; + +namespace Microsoft.AspNetCore.Testing.xunit +{ + /// + /// Skips a test if the OS is the given type (Windows) and the OS version is less than specified. + /// E.g. Specifying Window 10.0 skips on Win 8, but not on Linux. Combine with OSSkipConditionAttribute as needed. + /// + [AttributeUsage(AttributeTargets.Method | AttributeTargets.Class | AttributeTargets.Assembly, AllowMultiple = true)] + public class MinimumOSVersionAttribute : Attribute, ITestCondition + { + private readonly OperatingSystems _excludedOperatingSystem; + private readonly Version _minVersion; + private readonly OperatingSystems _osPlatform; + private readonly Version _osVersion; + + public MinimumOSVersionAttribute(OperatingSystems operatingSystem, string minVersion) : + this( + operatingSystem, + GetCurrentOS(), + GetCurrentOSVersion(), + Version.Parse(minVersion)) + { + } + + // to enable unit testing + internal MinimumOSVersionAttribute( + OperatingSystems operatingSystem, OperatingSystems osPlatform, Version osVersion, Version minVersion) + { + if (operatingSystem != OperatingSystems.Windows) + { + throw new NotImplementedException("Min version support is only implemented for Windows."); + } + _excludedOperatingSystem = operatingSystem; + _minVersion = minVersion; + _osPlatform = osPlatform; + _osVersion = osVersion; + + SkipReason = $"This test requires {_excludedOperatingSystem} {_minVersion} or later."; + } + + public bool IsMet + { + get + { + // Do not skip other OS's, Use OSSkipConditionAttribute or a separate MinimumOSVersionAttribute for that. + if (_osPlatform != _excludedOperatingSystem) + { + return true; + } + + return _osVersion >= _minVersion; + } + } + + public string SkipReason { get; set; } + + private static OperatingSystems GetCurrentOS() + { + if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) + { + return OperatingSystems.Windows; + } + else if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux)) + { + return OperatingSystems.Linux; + } + else if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX)) + { + return OperatingSystems.MacOSX; + } + throw new PlatformNotSupportedException(); + } + + private static Version GetCurrentOSVersion() + { + // currently not used on other OS's + if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) + { + // Win10+ + var key = Registry.LocalMachine.OpenSubKey(@"SOFTWARE\Microsoft\Windows NT\CurrentVersion"); + var major = key.GetValue("CurrentMajorVersionNumber") as int?; + var minor = key.GetValue("CurrentMinorVersionNumber") as int?; + + if (major.HasValue && minor.HasValue) + { + return new Version(major.Value, minor.Value); + } + + // CurrentVersion doesn't work past Win8.1 + var current = key.GetValue("CurrentVersion") as string; + if (!string.IsNullOrEmpty(current) && Version.TryParse(current, out var currentVersion)) + { + return currentVersion; + } + + // Environment.OSVersion doesn't work past Win8. + return Environment.OSVersion.Version; + } + else + { + return new Version(); + } + } + } +} diff --git a/src/Testing/src/xunit/OSSkipConditionAttribute.cs b/src/Testing/src/xunit/OSSkipConditionAttribute.cs new file mode 100644 index 0000000000..9996510718 --- /dev/null +++ b/src/Testing/src/xunit/OSSkipConditionAttribute.cs @@ -0,0 +1,99 @@ +// 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.Collections.Generic; +using System.Linq; +using System.Runtime.InteropServices; + +namespace Microsoft.AspNetCore.Testing.xunit +{ + [AttributeUsage(AttributeTargets.Method | AttributeTargets.Class | AttributeTargets.Assembly, AllowMultiple = true)] + public class OSSkipConditionAttribute : Attribute, ITestCondition + { + private readonly OperatingSystems _excludedOperatingSystem; + private readonly IEnumerable _excludedVersions; + private readonly OperatingSystems _osPlatform; + private readonly string _osVersion; + + public OSSkipConditionAttribute(OperatingSystems operatingSystem, params string[] versions) : + this( + operatingSystem, + GetCurrentOS(), + GetCurrentOSVersion(), + versions) + { + } + + // to enable unit testing + internal OSSkipConditionAttribute( + OperatingSystems operatingSystem, OperatingSystems osPlatform, string osVersion, params string[] versions) + { + _excludedOperatingSystem = operatingSystem; + _excludedVersions = versions ?? Enumerable.Empty(); + _osPlatform = osPlatform; + _osVersion = osVersion; + } + + public bool IsMet + { + get + { + var currentOSInfo = new OSInfo() + { + OperatingSystem = _osPlatform, + Version = _osVersion, + }; + + var skip = (_excludedOperatingSystem & currentOSInfo.OperatingSystem) == currentOSInfo.OperatingSystem; + if (_excludedVersions.Any()) + { + skip = skip + && _excludedVersions.Any(ex => _osVersion.StartsWith(ex, StringComparison.OrdinalIgnoreCase)); + } + + // Since a test would be excuted only if 'IsMet' is true, return false if we want to skip + return !skip; + } + } + + public string SkipReason { get; set; } = "Test cannot run on this operating system."; + + static private OperatingSystems GetCurrentOS() + { + if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) + { + return OperatingSystems.Windows; + } + else if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux)) + { + return OperatingSystems.Linux; + } + else if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX)) + { + return OperatingSystems.MacOSX; + } + throw new PlatformNotSupportedException(); + } + + static private string GetCurrentOSVersion() + { + // currently not used on other OS's + if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) + { + return Environment.OSVersion.Version.ToString(); + } + else + { + return string.Empty; + } + } + + private class OSInfo + { + public OperatingSystems OperatingSystem { get; set; } + + public string Version { get; set; } + } + } +} diff --git a/src/Testing/src/xunit/OperatingSystems.cs b/src/Testing/src/xunit/OperatingSystems.cs new file mode 100644 index 0000000000..c575d3e197 --- /dev/null +++ b/src/Testing/src/xunit/OperatingSystems.cs @@ -0,0 +1,15 @@ +// 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; + +namespace Microsoft.AspNetCore.Testing.xunit +{ + [Flags] + public enum OperatingSystems + { + Linux = 1, + MacOSX = 2, + Windows = 4, + } +} \ No newline at end of file diff --git a/src/Testing/src/xunit/RuntimeFrameworks.cs b/src/Testing/src/xunit/RuntimeFrameworks.cs new file mode 100644 index 0000000000..2ec5ea7ec1 --- /dev/null +++ b/src/Testing/src/xunit/RuntimeFrameworks.cs @@ -0,0 +1,16 @@ +// 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; + +namespace Microsoft.AspNetCore.Testing.xunit +{ + [Flags] + public enum RuntimeFrameworks + { + None = 0, + Mono = 1 << 0, + CLR = 1 << 1, + CoreCLR = 1 << 2 + } +} \ No newline at end of file diff --git a/src/Testing/src/xunit/SkippedTestCase.cs b/src/Testing/src/xunit/SkippedTestCase.cs new file mode 100644 index 0000000000..c2e15fa640 --- /dev/null +++ b/src/Testing/src/xunit/SkippedTestCase.cs @@ -0,0 +1,40 @@ +// 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 Xunit.Abstractions; +using Xunit.Sdk; + +namespace Microsoft.AspNetCore.Testing.xunit +{ + public class SkippedTestCase : XunitTestCase + { + private string _skipReason; + + [Obsolete("Called by the de-serializer; should only be called by deriving classes for de-serialization purposes")] + public SkippedTestCase() : base() + { + } + + public SkippedTestCase(string skipReason, IMessageSink diagnosticMessageSink, TestMethodDisplay defaultMethodDisplay, ITestMethod testMethod, object[] testMethodArguments = null) + : base(diagnosticMessageSink, defaultMethodDisplay, testMethod, testMethodArguments) + { + _skipReason = skipReason; + } + + protected override string GetSkipReason(IAttributeInfo factAttribute) + => _skipReason ?? base.GetSkipReason(factAttribute); + + public override void Deserialize(IXunitSerializationInfo data) + { + base.Deserialize(data); + _skipReason = data.GetValue(nameof(_skipReason)); + } + + public override void Serialize(IXunitSerializationInfo data) + { + base.Serialize(data); + data.AddValue(nameof(_skipReason), _skipReason); + } + } +} \ No newline at end of file diff --git a/src/Testing/src/xunit/TestMethodExtensions.cs b/src/Testing/src/xunit/TestMethodExtensions.cs new file mode 100644 index 0000000000..5ec3bb4ec3 --- /dev/null +++ b/src/Testing/src/xunit/TestMethodExtensions.cs @@ -0,0 +1,34 @@ +// 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.Linq; +using Xunit.Abstractions; +using Xunit.Sdk; + +namespace Microsoft.AspNetCore.Testing.xunit +{ + public static class TestMethodExtensions + { + public static string EvaluateSkipConditions(this ITestMethod testMethod) + { + var testClass = testMethod.TestClass.Class; + var assembly = testMethod.TestClass.TestCollection.TestAssembly.Assembly; + var conditionAttributes = testMethod.Method + .GetCustomAttributes(typeof(ITestCondition)) + .Concat(testClass.GetCustomAttributes(typeof(ITestCondition))) + .Concat(assembly.GetCustomAttributes(typeof(ITestCondition))) + .OfType() + .Select(attributeInfo => attributeInfo.Attribute); + + foreach (ITestCondition condition in conditionAttributes) + { + if (!condition.IsMet) + { + return condition.SkipReason; + } + } + + return null; + } + } +} diff --git a/src/Testing/src/xunit/WindowsVersions.cs b/src/Testing/src/xunit/WindowsVersions.cs new file mode 100644 index 0000000000..ff8312b363 --- /dev/null +++ b/src/Testing/src/xunit/WindowsVersions.cs @@ -0,0 +1,18 @@ +// 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. + +namespace Microsoft.AspNetCore.Testing.xunit +{ + public static class WindowsVersions + { + public const string Win7 = "6.1"; + + public const string Win2008R2 = Win7; + + public const string Win8 = "6.2"; + + public const string Win81 = "6.3"; + + public const string Win10 = "10.0"; + } +} diff --git a/src/Testing/test/CollectingEventListenerTest.cs b/src/Testing/test/CollectingEventListenerTest.cs new file mode 100644 index 0000000000..8f131982f0 --- /dev/null +++ b/src/Testing/test/CollectingEventListenerTest.cs @@ -0,0 +1,87 @@ +using System.Diagnostics.Tracing; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Testing.Tracing; +using Xunit; + +namespace Microsoft.AspNetCore.Testing.Tests +{ + // We are verifying here that when event listener tests are spread among multiple classes, they still + // work, even when run in parallel. To do that we have a bunch of tests in different classes (since + // that affects parallelism) and do some Task.Yielding in them. + public class CollectingEventListenerTests + { + public abstract class CollectingTestBase : EventSourceTestBase + { + [Fact] + public async Task CollectingEventListenerTest() + { + CollectFrom("Microsoft-AspNetCore-Testing-Test"); + + await Task.Yield(); + TestEventSource.Log.Test(); + await Task.Yield(); + TestEventSource.Log.TestWithPayload(42, 4.2); + await Task.Yield(); + + var events = GetEvents(); + EventAssert.Collection(events, + EventAssert.Event(1, "Test", EventLevel.Informational), + EventAssert.Event(2, "TestWithPayload", EventLevel.Verbose) + .Payload("payload1", 42) + .Payload("payload2", 4.2)); + } + } + + // These tests are designed to interfere with the collecting ones by running in parallel and writing events + public abstract class NonCollectingTestBase + { + [Fact] + public async Task CollectingEventListenerTest() + { + await Task.Yield(); + TestEventSource.Log.Test(); + await Task.Yield(); + TestEventSource.Log.TestWithPayload(42, 4.2); + await Task.Yield(); + } + } + + public class CollectingTests + { + public class A : CollectingTestBase { } + public class B : CollectingTestBase { } + public class C : CollectingTestBase { } + public class D : CollectingTestBase { } + public class E : CollectingTestBase { } + public class F : CollectingTestBase { } + public class G : CollectingTestBase { } + } + + public class NonCollectingTests + { + public class A : NonCollectingTestBase { } + public class B : NonCollectingTestBase { } + public class C : NonCollectingTestBase { } + public class D : NonCollectingTestBase { } + public class E : NonCollectingTestBase { } + public class F : NonCollectingTestBase { } + public class G : NonCollectingTestBase { } + } + } + + [EventSource(Name = "Microsoft-AspNetCore-Testing-Test")] + public class TestEventSource : EventSource + { + public static readonly TestEventSource Log = new TestEventSource(); + + private TestEventSource() + { + } + + [Event(eventId: 1, Level = EventLevel.Informational, Message = "Test")] + public void Test() => WriteEvent(1); + + [Event(eventId: 2, Level = EventLevel.Verbose, Message = "Test")] + public void TestWithPayload(int payload1, double payload2) => WriteEvent(2, payload1, payload2); + } +} diff --git a/src/Testing/test/ConditionalFactTest.cs b/src/Testing/test/ConditionalFactTest.cs new file mode 100644 index 0000000000..a04eb1731d --- /dev/null +++ b/src/Testing/test/ConditionalFactTest.cs @@ -0,0 +1,60 @@ +// 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 Microsoft.AspNetCore.Testing.xunit; +using Xunit; + +namespace Microsoft.AspNetCore.Testing +{ + public class ConditionalFactTest : IClassFixture + { + public ConditionalFactTest(ConditionalFactAsserter collector) + { + Asserter = collector; + } + + private ConditionalFactAsserter Asserter { get; } + + [Fact] + public void TestAlwaysRun() + { + // This is required to ensure that the type at least gets initialized. + Assert.True(true); + } + + [ConditionalFact(Skip = "Test is always skipped.")] + public void ConditionalFactSkip() + { + Assert.True(false, "This test should always be skipped."); + } + +#if NETCOREAPP2_2 + [ConditionalFact] + [FrameworkSkipCondition(RuntimeFrameworks.CLR)] + public void ThisTestMustRunOnCoreCLR() + { + Asserter.TestRan = true; + } +#elif NET461 || NET46 + [ConditionalFact] + [FrameworkSkipCondition(RuntimeFrameworks.CoreCLR)] + public void ThisTestMustRunOnCLR() + { + Asserter.TestRan = true; + } +#else +#error Target frameworks need to be updated. +#endif + + public class ConditionalFactAsserter : IDisposable + { + public bool TestRan { get; set; } + + public void Dispose() + { + Assert.True(TestRan, "If this assertion fails, a conditional fact wasn't discovered."); + } + } + } +} \ No newline at end of file diff --git a/src/Testing/test/ConditionalTheoryTest.cs b/src/Testing/test/ConditionalTheoryTest.cs new file mode 100644 index 0000000000..1181f1365a --- /dev/null +++ b/src/Testing/test/ConditionalTheoryTest.cs @@ -0,0 +1,156 @@ +// 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 Microsoft.AspNetCore.Testing.xunit; +using Xunit; +using Xunit.Abstractions; + +namespace Microsoft.AspNetCore.Testing +{ + public class ConditionalTheoryTest : IClassFixture + { + public ConditionalTheoryTest(ConditionalTheoryAsserter asserter) + { + Asserter = asserter; + } + + public ConditionalTheoryAsserter Asserter { get; } + + [ConditionalTheory(Skip = "Test is always skipped.")] + [InlineData(0)] + public void ConditionalTheorySkip(int arg) + { + Assert.True(false, "This test should always be skipped."); + } + + private static int _conditionalTheoryRuns = 0; + + [ConditionalTheory] + [InlineData(0)] + [InlineData(1)] + [InlineData(2, Skip = "Skip these data")] + public void ConditionalTheoryRunOncePerDataLine(int arg) + { + _conditionalTheoryRuns++; + Assert.True(_conditionalTheoryRuns <= 2, $"Theory should run 2 times, but ran {_conditionalTheoryRuns} times."); + } + + [ConditionalTheory, Trait("Color", "Blue")] + [InlineData(1)] + public void ConditionalTheoriesShouldPreserveTraits(int arg) + { + Assert.True(true); + } + + [ConditionalTheory(Skip = "Skip this")] + [MemberData(nameof(GetInts))] + public void ConditionalTheoriesWithSkippedMemberData(int arg) + { + Assert.True(false, "This should never run"); + } + + private static int _conditionalMemberDataRuns = 0; + + [ConditionalTheory] + [InlineData(4)] + [MemberData(nameof(GetInts))] + public void ConditionalTheoriesWithMemberData(int arg) + { + _conditionalMemberDataRuns++; + Assert.True(_conditionalTheoryRuns <= 3, $"Theory should run 2 times, but ran {_conditionalMemberDataRuns} times."); + } + + public static TheoryData GetInts + => new TheoryData { 0, 1 }; + + [ConditionalTheory] + [OSSkipCondition(OperatingSystems.Windows)] + [OSSkipCondition(OperatingSystems.MacOSX)] + [OSSkipCondition(OperatingSystems.Linux)] + [MemberData(nameof(GetActionTestData))] + public void ConditionalTheoryWithFuncs(Func func) + { + Assert.True(false, "This should never run"); + } + + [Fact] + public void TestAlwaysRun() + { + // This is required to ensure that this type at least gets initialized. + Assert.True(true); + } + +#if NETCOREAPP2_2 + [ConditionalTheory] + [FrameworkSkipCondition(RuntimeFrameworks.CLR)] + [MemberData(nameof(GetInts))] + public void ThisTestMustRunOnCoreCLR(int value) + { + Asserter.TestRan = true; + } +#elif NET461 || NET46 + [ConditionalTheory] + [FrameworkSkipCondition(RuntimeFrameworks.CoreCLR)] + [MemberData(nameof(GetInts))] + public void ThisTestMustRunOnCLR(int value) + { + Asserter.TestRan = true; + } +#else +#error Target frameworks need to be updated. +#endif + + public static TheoryData> GetActionTestData + => new TheoryData> + { + (i) => i * 1 + }; + + public class ConditionalTheoryAsserter : IDisposable + { + public bool TestRan { get; set; } + + public void Dispose() + { + Assert.True(TestRan, "If this assertion fails, a conditional theory wasn't discovered."); + } + } + + [ConditionalTheory] + [MemberData(nameof(SkippableData))] + public void WithSkipableData(Skippable skippable) + { + Assert.Null(skippable.Skip); + Assert.Equal(1, skippable.Data); + } + + public static TheoryData SkippableData => new TheoryData + { + new Skippable() { Data = 1 }, + new Skippable() { Data = 2, Skip = "This row should be skipped." } + }; + + public class Skippable : IXunitSerializable + { + public Skippable() { } + public int Data { get; set; } + public string Skip { get; set; } + + public void Serialize(IXunitSerializationInfo info) + { + info.AddValue(nameof(Data), Data, typeof(int)); + } + + public void Deserialize(IXunitSerializationInfo info) + { + Data = info.GetValue(nameof(Data)); + } + + public override string ToString() + { + return Data.ToString(); + } + } + } +} diff --git a/src/Testing/test/DockerTests.cs b/src/Testing/test/DockerTests.cs new file mode 100644 index 0000000000..c66fdd679c --- /dev/null +++ b/src/Testing/test/DockerTests.cs @@ -0,0 +1,21 @@ +// 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.InteropServices; +using Microsoft.AspNetCore.Testing.xunit; +using Xunit; + +namespace Microsoft.AspNetCore.Testing +{ + public class DockerTests + { + [ConditionalFact] + [DockerOnly] + [Trait("Docker", "true")] + public void DoesNotRunOnWindows() + { + Assert.False(RuntimeInformation.IsOSPlatform(OSPlatform.Windows)); + } + } +} diff --git a/src/Testing/test/EnvironmentVariableSkipConditionTest.cs b/src/Testing/test/EnvironmentVariableSkipConditionTest.cs new file mode 100644 index 0000000000..b536ae56f7 --- /dev/null +++ b/src/Testing/test/EnvironmentVariableSkipConditionTest.cs @@ -0,0 +1,166 @@ +// 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 Xunit; + +namespace Microsoft.AspNetCore.Testing.xunit +{ + public class EnvironmentVariableSkipConditionTest + { + private readonly string _skipReason = "Test skipped on environment variable with name '{0}' and value '{1}'" + + $" for the '{nameof(EnvironmentVariableSkipConditionAttribute.SkipOnMatch)}' value of '{{2}}'."; + + [Theory] + [InlineData("false")] + [InlineData("")] + [InlineData(null)] + public void IsMet_DoesNotMatch(string environmentVariableValue) + { + // Arrange + var attribute = new EnvironmentVariableSkipConditionAttribute( + new TestEnvironmentVariable(environmentVariableValue), + "Run", + "true"); + + // Act + var isMet = attribute.IsMet; + + // Assert + Assert.False(isMet); + } + + [Theory] + [InlineData("True")] + [InlineData("TRUE")] + [InlineData("true")] + public void IsMet_DoesCaseInsensitiveMatch_OnValue(string environmentVariableValue) + { + // Arrange + var attribute = new EnvironmentVariableSkipConditionAttribute( + new TestEnvironmentVariable(environmentVariableValue), + "Run", + "true"); + + // Act + var isMet = attribute.IsMet; + + // Assert + Assert.True(isMet); + Assert.Equal( + string.Format(_skipReason, "Run", environmentVariableValue, attribute.SkipOnMatch), + attribute.SkipReason); + } + + [Fact] + public void IsMet_DoesSuccessfulMatch_OnNull() + { + // Arrange + var attribute = new EnvironmentVariableSkipConditionAttribute( + new TestEnvironmentVariable(null), + "Run", + "true", null); // skip the test when the variable 'Run' is explicitly set to 'true' or is null (default) + + // Act + var isMet = attribute.IsMet; + + // Assert + Assert.True(isMet); + Assert.Equal( + string.Format(_skipReason, "Run", "(null)", attribute.SkipOnMatch), + attribute.SkipReason); + } + + [Theory] + [InlineData("false")] + [InlineData("")] + [InlineData(null)] + public void IsMet_MatchesOnMultipleSkipValues(string environmentVariableValue) + { + // Arrange + var attribute = new EnvironmentVariableSkipConditionAttribute( + new TestEnvironmentVariable(environmentVariableValue), + "Run", + "false", "", null); + + // Act + var isMet = attribute.IsMet; + + // Assert + Assert.True(isMet); + } + + [Fact] + public void IsMet_DoesNotMatch_OnMultipleSkipValues() + { + // Arrange + var attribute = new EnvironmentVariableSkipConditionAttribute( + new TestEnvironmentVariable("100"), + "Build", + "125", "126"); + + // Act + var isMet = attribute.IsMet; + + // Assert + Assert.False(isMet); + } + + [Theory] + [InlineData("CentOS")] + [InlineData(null)] + [InlineData("")] + public void IsMet_Matches_WhenSkipOnMatchIsFalse(string environmentVariableValue) + { + // Arrange + var attribute = new EnvironmentVariableSkipConditionAttribute( + new TestEnvironmentVariable(environmentVariableValue), + "LinuxFlavor", + "Ubuntu14.04") + { + // Example: Run this test on all OSes except on "Ubuntu14.04" + SkipOnMatch = false + }; + + // Act + var isMet = attribute.IsMet; + + // Assert + Assert.True(isMet); + } + + [Fact] + public void IsMet_DoesNotMatch_WhenSkipOnMatchIsFalse() + { + // Arrange + var attribute = new EnvironmentVariableSkipConditionAttribute( + new TestEnvironmentVariable("Ubuntu14.04"), + "LinuxFlavor", + "Ubuntu14.04") + { + // Example: Run this test on all OSes except on "Ubuntu14.04" + SkipOnMatch = false + }; + + // Act + var isMet = attribute.IsMet; + + // Assert + Assert.False(isMet); + } + + private struct TestEnvironmentVariable : IEnvironmentVariable + { + public TestEnvironmentVariable(string value) + { + Value = value; + } + + public string Value { get; private set; } + + public string Get(string name) + { + return Value; + } + } + } +} diff --git a/src/Testing/test/ExceptionAssertTest.cs b/src/Testing/test/ExceptionAssertTest.cs new file mode 100644 index 0000000000..aa7354dca8 --- /dev/null +++ b/src/Testing/test/ExceptionAssertTest.cs @@ -0,0 +1,39 @@ +// 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 Xunit; + +namespace Microsoft.AspNetCore.Testing +{ + public class ExceptionAssertTest + { + [Fact] + [ReplaceCulture("fr-FR", "fr-FR")] + public void AssertArgumentNullOrEmptyString_WorksInNonEnglishCultures() + { + // Arrange + Action action = () => + { + throw new ArgumentException("Value cannot be null or an empty string.", "foo"); + }; + + // Act and Assert + ExceptionAssert.ThrowsArgumentNullOrEmptyString(action, "foo"); + } + + [Fact] + [ReplaceCulture("fr-FR", "fr-FR")] + public void AssertArgumentOutOfRangeException_WorksInNonEnglishCultures() + { + // Arrange + Action action = () => + { + throw new ArgumentOutOfRangeException("foo", 10, "exception message."); + }; + + // Act and Assert + ExceptionAssert.ThrowsArgumentOutOfRange(action, "foo", "exception message.", 10); + } + } +} \ No newline at end of file diff --git a/src/Testing/test/HttpClientSlimTest.cs b/src/Testing/test/HttpClientSlimTest.cs new file mode 100644 index 0000000000..42b19ece08 --- /dev/null +++ b/src/Testing/test/HttpClientSlimTest.cs @@ -0,0 +1,117 @@ +// 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.Net; +using System.Net.Http; +using System.Net.Sockets; +using System.Text; +using System.Threading.Tasks; +using Xunit; + +namespace Microsoft.AspNetCore.Testing +{ + public class HttpClientSlimTest + { + private static byte[] _defaultResponse = Encoding.ASCII.GetBytes("test"); + + [Fact] + public async Task GetStringAsyncHttp() + { + using (var host = StartHost(out var address)) + { + Assert.Equal("test", await HttpClientSlim.GetStringAsync(address)); + } + } + + [Fact] + public async Task GetStringAsyncThrowsForErrorResponse() + { + using (var host = StartHost(out var address, statusCode: 500)) + { + await Assert.ThrowsAnyAsync(() => HttpClientSlim.GetStringAsync(address)); + } + } + + [Fact] + public async Task PostAsyncHttp() + { + using (var host = StartHost(out var address, handler: context => context.Request.InputStream.CopyToAsync(context.Response.OutputStream))) + { + Assert.Equal("test post", await HttpClientSlim.PostAsync(address, new StringContent("test post"))); + } + } + + [Fact] + public async Task PostAsyncThrowsForErrorResponse() + { + using (var host = StartHost(out var address, statusCode: 500)) + { + await Assert.ThrowsAnyAsync( + () => HttpClientSlim.PostAsync(address, new StringContent(""))); + } + } + + [Fact] + public void Ipv6ScopeIdsFilteredOut() + { + var requestUri = new Uri("http://[fe80::5d2a:d070:6fd6:1bac%7]:5003/"); + Assert.Equal("[fe80::5d2a:d070:6fd6:1bac]:5003", HttpClientSlim.GetHost(requestUri)); + } + + [Fact] + public void GetHostExcludesDefaultPort() + { + var requestUri = new Uri("http://[fe80::5d2a:d070:6fd6:1bac%7]:80/"); + Assert.Equal("[fe80::5d2a:d070:6fd6:1bac]", HttpClientSlim.GetHost(requestUri)); + } + + private HttpListener StartHost(out string address, int statusCode = 200, Func handler = null) + { + var listener = new HttpListener(); + var random = new Random(); + address = null; + + for (var i = 0; i < 10; i++) + { + try + { + // HttpListener doesn't support requesting port 0 (dynamic). + // Requesting port 0 from Sockets and then passing that to HttpListener is racy. + // Just keep trying until we find a free one. + address = $"http://127.0.0.1:{random.Next(1024, ushort.MaxValue)}/"; + listener.Prefixes.Add(address); + listener.Start(); + break; + } + catch (HttpListenerException) + { + // Address in use + listener.Close(); + listener = new HttpListener(); + } + } + + Assert.True(listener.IsListening, "IsListening"); + + _ = listener.GetContextAsync().ContinueWith(async task => + { + var context = task.Result; + context.Response.StatusCode = statusCode; + + if (handler == null) + { + await context.Response.OutputStream.WriteAsync(_defaultResponse, 0, _defaultResponse.Length); + } + else + { + await handler(context); + } + + context.Response.Close(); + }); + + return listener; + } + } +} diff --git a/src/Testing/test/Microsoft.AspNetCore.Testing.Tests.csproj b/src/Testing/test/Microsoft.AspNetCore.Testing.Tests.csproj new file mode 100644 index 0000000000..3fdd9ff379 --- /dev/null +++ b/src/Testing/test/Microsoft.AspNetCore.Testing.Tests.csproj @@ -0,0 +1,28 @@ + + + + $(StandardTestTfms) + + + $(NoWarn);xUnit1004 + + $(NoWarn);xUnit1026 + + + + + + + + + + + + + + + + + + + diff --git a/src/Testing/test/OSSkipConditionAttributeTest.cs b/src/Testing/test/OSSkipConditionAttributeTest.cs new file mode 100644 index 0000000000..0120eb7a4c --- /dev/null +++ b/src/Testing/test/OSSkipConditionAttributeTest.cs @@ -0,0 +1,132 @@ +// 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.InteropServices; +using Xunit; + +namespace Microsoft.AspNetCore.Testing.xunit +{ + public class OSSkipConditionAttributeTest + { + [Fact] + public void Skips_WhenOnlyOperatingSystemIsSupplied() + { + // Act + var osSkipAttribute = new OSSkipConditionAttribute( + OperatingSystems.Windows, + OperatingSystems.Windows, + "2.5"); + + // Assert + Assert.False(osSkipAttribute.IsMet); + } + + [Fact] + public void DoesNotSkip_WhenOperatingSystemDoesNotMatch() + { + // Act + var osSkipAttribute = new OSSkipConditionAttribute( + OperatingSystems.Linux, + OperatingSystems.Windows, + "2.5"); + + // Assert + Assert.True(osSkipAttribute.IsMet); + } + + [Fact] + public void DoesNotSkip_WhenVersionsDoNotMatch() + { + // Act + var osSkipAttribute = new OSSkipConditionAttribute( + OperatingSystems.Windows, + OperatingSystems.Windows, + "2.5", + "10.0"); + + // Assert + Assert.True(osSkipAttribute.IsMet); + } + + [Fact] + public void DoesNotSkip_WhenOnlyVersionsMatch() + { + // Act + var osSkipAttribute = new OSSkipConditionAttribute( + OperatingSystems.Linux, + OperatingSystems.Windows, + "2.5", + "2.5"); + + // Assert + Assert.True(osSkipAttribute.IsMet); + } + + [Theory] + [InlineData("2.5", "2.5")] + [InlineData("blue", "Blue")] + public void Skips_WhenVersionsMatches(string currentOSVersion, string skipVersion) + { + // Act + var osSkipAttribute = new OSSkipConditionAttribute( + OperatingSystems.Windows, + OperatingSystems.Windows, + currentOSVersion, + skipVersion); + + // Assert + Assert.False(osSkipAttribute.IsMet); + } + + [Fact] + public void Skips_WhenVersionsMatchesOutOfMultiple() + { + // Act + var osSkipAttribute = new OSSkipConditionAttribute( + OperatingSystems.Windows, + OperatingSystems.Windows, + "2.5", + "10.0", "3.4", "2.5"); + + // Assert + Assert.False(osSkipAttribute.IsMet); + } + + [Fact] + public void Skips_BothMacOSXAndLinux() + { + // Act + var osSkipAttributeLinux = new OSSkipConditionAttribute(OperatingSystems.Linux | OperatingSystems.MacOSX, OperatingSystems.Linux, string.Empty); + var osSkipAttributeMacOSX = new OSSkipConditionAttribute(OperatingSystems.Linux | OperatingSystems.MacOSX, OperatingSystems.MacOSX, string.Empty); + + // Assert + Assert.False(osSkipAttributeLinux.IsMet); + Assert.False(osSkipAttributeMacOSX.IsMet); + } + + [Fact] + public void Skips_BothMacOSXAndWindows() + { + // Act + var osSkipAttribute = new OSSkipConditionAttribute(OperatingSystems.Windows | OperatingSystems.MacOSX, OperatingSystems.Windows, string.Empty); + var osSkipAttributeMacOSX = new OSSkipConditionAttribute(OperatingSystems.Windows | OperatingSystems.MacOSX, OperatingSystems.MacOSX, string.Empty); + + // Assert + Assert.False(osSkipAttribute.IsMet); + Assert.False(osSkipAttributeMacOSX.IsMet); + } + + [Fact] + public void Skips_BothWindowsAndLinux() + { + // Act + var osSkipAttribute = new OSSkipConditionAttribute(OperatingSystems.Linux | OperatingSystems.Windows, OperatingSystems.Windows, string.Empty); + var osSkipAttributeLinux = new OSSkipConditionAttribute(OperatingSystems.Linux | OperatingSystems.Windows, OperatingSystems.Linux, string.Empty); + + // Assert + Assert.False(osSkipAttribute.IsMet); + Assert.False(osSkipAttributeLinux.IsMet); + } + } +} diff --git a/src/Testing/test/OSSkipConditionTest.cs b/src/Testing/test/OSSkipConditionTest.cs new file mode 100644 index 0000000000..2d76f2c2cd --- /dev/null +++ b/src/Testing/test/OSSkipConditionTest.cs @@ -0,0 +1,116 @@ +// 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.InteropServices; +using Xunit; + +namespace Microsoft.AspNetCore.Testing.xunit +{ + public class OSSkipConditionTest + { + [ConditionalFact] + [OSSkipCondition(OperatingSystems.Linux)] + public void TestSkipLinux() + { + Assert.False( + RuntimeInformation.IsOSPlatform(OSPlatform.Linux), + "Test should not be running on Linux"); + } + + [ConditionalFact] + [OSSkipCondition(OperatingSystems.MacOSX)] + public void TestSkipMacOSX() + { + Assert.False( + RuntimeInformation.IsOSPlatform(OSPlatform.OSX), + "Test should not be running on MacOSX."); + } + + [ConditionalFact] + [OSSkipCondition(OperatingSystems.Windows, WindowsVersions.Win7, WindowsVersions.Win2008R2)] + public void RunTest_DoesNotRunOnWin7OrWin2008R2() + { + Assert.False( + RuntimeInformation.IsOSPlatform(OSPlatform.Windows) && + Environment.OSVersion.Version.ToString().StartsWith("6.1"), + "Test should not be running on Win7 or Win2008R2."); + } + + [ConditionalFact] + [OSSkipCondition(OperatingSystems.Windows)] + public void TestSkipWindows() + { + Assert.False( + RuntimeInformation.IsOSPlatform(OSPlatform.Windows), + "Test should not be running on Windows."); + } + + [ConditionalFact] + [OSSkipCondition(OperatingSystems.Linux | OperatingSystems.MacOSX)] + public void TestSkipLinuxAndMacOSX() + { + Assert.False( + RuntimeInformation.IsOSPlatform(OSPlatform.Linux), + "Test should not be running on Linux."); + Assert.False( + RuntimeInformation.IsOSPlatform(OSPlatform.OSX), + "Test should not be running on MacOSX."); + } + + [ConditionalTheory] + [OSSkipCondition(OperatingSystems.Linux)] + [InlineData(1)] + public void TestTheorySkipLinux(int arg) + { + Assert.False( + RuntimeInformation.IsOSPlatform(OSPlatform.Linux), + "Test should not be running on Linux"); + } + + [ConditionalTheory] + [OSSkipCondition(OperatingSystems.MacOSX)] + [InlineData(1)] + public void TestTheorySkipMacOS(int arg) + { + Assert.False( + RuntimeInformation.IsOSPlatform(OSPlatform.OSX), + "Test should not be running on MacOSX."); + } + + [ConditionalTheory] + [OSSkipCondition(OperatingSystems.Windows)] + [InlineData(1)] + public void TestTheorySkipWindows(int arg) + { + Assert.False( + RuntimeInformation.IsOSPlatform(OSPlatform.Windows), + "Test should not be running on Windows."); + } + + [ConditionalTheory] + [OSSkipCondition(OperatingSystems.Linux | OperatingSystems.MacOSX)] + [InlineData(1)] + public void TestTheorySkipLinuxAndMacOSX(int arg) + { + Assert.False( + RuntimeInformation.IsOSPlatform(OSPlatform.Linux), + "Test should not be running on Linux."); + Assert.False( + RuntimeInformation.IsOSPlatform(OSPlatform.OSX), + "Test should not be running on MacOSX."); + } + } + + [OSSkipCondition(OperatingSystems.Windows)] + public class OSSkipConditionClassTest + { + [ConditionalFact] + public void TestSkipClassWindows() + { + Assert.False( + RuntimeInformation.IsOSPlatform(OSPlatform.Windows), + "Test should not be running on Windows."); + } + } +} diff --git a/src/Testing/test/ReplaceCultureAttributeTest.cs b/src/Testing/test/ReplaceCultureAttributeTest.cs new file mode 100644 index 0000000000..6b8df346c9 --- /dev/null +++ b/src/Testing/test/ReplaceCultureAttributeTest.cs @@ -0,0 +1,66 @@ +// 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.Globalization; +using Xunit; + +namespace Microsoft.AspNetCore.Testing +{ + public class RepalceCultureAttributeTest + { + [Fact] + public void DefaultsTo_EnGB_EnUS() + { + // Arrange + var culture = new CultureInfo("en-GB"); + var uiCulture = new CultureInfo("en-US"); + + // Act + var replaceCulture = new ReplaceCultureAttribute(); + + // Assert + Assert.Equal(culture, replaceCulture.Culture); + Assert.Equal(uiCulture, replaceCulture.UICulture); + } + + [Fact] + public void UsesSuppliedCultureAndUICulture() + { + // Arrange + var culture = "de-DE"; + var uiCulture = "fr-CA"; + + // Act + var replaceCulture = new ReplaceCultureAttribute(culture, uiCulture); + + // Assert + Assert.Equal(new CultureInfo(culture), replaceCulture.Culture); + Assert.Equal(new CultureInfo(uiCulture), replaceCulture.UICulture); + } + + [Fact] + public void BeforeAndAfterTest_ReplacesCulture() + { + // Arrange + var originalCulture = CultureInfo.CurrentCulture; + var originalUICulture = CultureInfo.CurrentUICulture; + var culture = "de-DE"; + var uiCulture = "fr-CA"; + var replaceCulture = new ReplaceCultureAttribute(culture, uiCulture); + + // Act + replaceCulture.Before(methodUnderTest: null); + + // Assert + Assert.Equal(new CultureInfo(culture), CultureInfo.CurrentCulture); + Assert.Equal(new CultureInfo(uiCulture), CultureInfo.CurrentUICulture); + + // Act + replaceCulture.After(methodUnderTest: null); + + // Assert + Assert.Equal(originalCulture, CultureInfo.CurrentCulture); + Assert.Equal(originalUICulture, CultureInfo.CurrentUICulture); + } + } +} \ No newline at end of file diff --git a/src/Testing/test/TaskExtensionsTest.cs b/src/Testing/test/TaskExtensionsTest.cs new file mode 100644 index 0000000000..f7ad603df5 --- /dev/null +++ b/src/Testing/test/TaskExtensionsTest.cs @@ -0,0 +1,18 @@ +// 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.Threading.Tasks; +using Xunit; + +namespace Microsoft.AspNetCore.Testing +{ + public class TaskExtensionsTest + { + [Fact] + public async Task TimeoutAfterTest() + { + await Assert.ThrowsAsync(async () => await Task.Delay(1000).TimeoutAfter(TimeSpan.FromMilliseconds(50))); + } + } +} diff --git a/src/Testing/test/TestPathUtilitiesTest.cs b/src/Testing/test/TestPathUtilitiesTest.cs new file mode 100644 index 0000000000..0c9a7c5ee4 --- /dev/null +++ b/src/Testing/test/TestPathUtilitiesTest.cs @@ -0,0 +1,31 @@ +// 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.IO; +using Xunit; + +namespace Microsoft.AspNetCore.Testing +{ + public class TestPathUtilitiesTest + { + [Fact] + public void GetSolutionRootDirectory_ResolvesSolutionRoot() + { + // Directory.GetCurrentDirectory() gives: + // Testing\test\Microsoft.AspNetCore.Testing.Tests\bin\Debug\netcoreapp2.0 + // Testing\test\Microsoft.AspNetCore.Testing.Tests\bin\Debug\net461 + // Testing\test\Microsoft.AspNetCore.Testing.Tests\bin\Debug\net46 + var expectedPath = Path.GetFullPath(Path.Combine(Directory.GetCurrentDirectory(), "..", "..", "..", "..", "..", "..", "..")); + + Assert.Equal(expectedPath, TestPathUtilities.GetSolutionRootDirectory("Extensions")); + } + + [Fact] + public void GetSolutionRootDirectory_Throws_IfNotFound() + { + var exception = Assert.Throws(() => TestPathUtilities.GetSolutionRootDirectory("NotTesting")); + Assert.Equal($"Solution file NotTesting.sln could not be found in {AppContext.BaseDirectory} or its parent directories.", exception.Message); + } + } +} diff --git a/src/Testing/test/TestPlatformHelperTest.cs b/src/Testing/test/TestPlatformHelperTest.cs new file mode 100644 index 0000000000..8e35e164d5 --- /dev/null +++ b/src/Testing/test/TestPlatformHelperTest.cs @@ -0,0 +1,55 @@ +// 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 Microsoft.AspNetCore.Testing.xunit; +using Xunit; + +namespace Microsoft.AspNetCore.Testing +{ + public class TestPlatformHelperTest + { + [ConditionalFact] + [OSSkipCondition(OperatingSystems.MacOSX)] + [OSSkipCondition(OperatingSystems.Windows)] + public void IsLinux_TrueOnLinux() + { + Assert.True(TestPlatformHelper.IsLinux); + Assert.False(TestPlatformHelper.IsMac); + Assert.False(TestPlatformHelper.IsWindows); + } + + [ConditionalFact] + [OSSkipCondition(OperatingSystems.Linux)] + [OSSkipCondition(OperatingSystems.Windows)] + public void IsMac_TrueOnMac() + { + Assert.False(TestPlatformHelper.IsLinux); + Assert.True(TestPlatformHelper.IsMac); + Assert.False(TestPlatformHelper.IsWindows); + } + + [ConditionalFact] + [OSSkipCondition(OperatingSystems.Linux)] + [OSSkipCondition(OperatingSystems.MacOSX)] + public void IsWindows_TrueOnWindows() + { + Assert.False(TestPlatformHelper.IsLinux); + Assert.False(TestPlatformHelper.IsMac); + Assert.True(TestPlatformHelper.IsWindows); + } + + [ConditionalFact] + [FrameworkSkipCondition(RuntimeFrameworks.CLR | RuntimeFrameworks.CoreCLR | RuntimeFrameworks.None)] + public void IsMono_TrueOnMono() + { + Assert.True(TestPlatformHelper.IsMono); + } + + [ConditionalFact] + [FrameworkSkipCondition(RuntimeFrameworks.Mono)] + public void IsMono_FalseElsewhere() + { + Assert.False(TestPlatformHelper.IsMono); + } + } +} From 5c9701e0b6640b06635e4c3d570bf77e6de79b2b Mon Sep 17 00:00:00 2001 From: Nate McMaster Date: Tue, 30 Oct 2018 17:16:51 -0700 Subject: [PATCH 0004/1101] Merge branch 'release/2.2' \n\nCommit migrated from https://github.com/dotnet/extensions/commit/34204b6bc41de865f5310f5f237781a57a83976c --- src/Shared/BenchmarkRunner/DefaultCoreConfig.cs | 2 +- src/Shared/CertificateGeneration/CertificateManager.cs | 8 ++++---- .../CertificateGeneration/EnsureCertificateResult.cs | 4 ++-- src/Shared/test/Shared.Tests/CertificateManagerTests.cs | 4 ++-- src/Shared/test/Shared.Tests/DotNetMuxerTests.cs | 2 +- src/Testing/test/ConditionalFactTest.cs | 4 ++-- src/Testing/test/ConditionalTheoryTest.cs | 2 +- 7 files changed, 13 insertions(+), 13 deletions(-) diff --git a/src/Shared/BenchmarkRunner/DefaultCoreConfig.cs b/src/Shared/BenchmarkRunner/DefaultCoreConfig.cs index 5e2bafd506..a61833ab26 100644 --- a/src/Shared/BenchmarkRunner/DefaultCoreConfig.cs +++ b/src/Shared/BenchmarkRunner/DefaultCoreConfig.cs @@ -31,7 +31,7 @@ namespace BenchmarkDotNet.Attributes #if NETCOREAPP2_1 .With(CsProjCoreToolchain.From(NetCoreAppSettings.NetCoreApp21)) #else - .With(CsProjCoreToolchain.From(new NetCoreAppSettings("netcoreapp2.2", null, ".NET Core 2.2"))) + .With(CsProjCoreToolchain.From(new NetCoreAppSettings("netcoreapp3.0", null, ".NET Core 3.0"))) #endif .With(new GcMode { Server = true }) .With(RunStrategy.Throughput)); diff --git a/src/Shared/CertificateGeneration/CertificateManager.cs b/src/Shared/CertificateGeneration/CertificateManager.cs index 952cf7c36d..26639572e4 100644 --- a/src/Shared/CertificateGeneration/CertificateManager.cs +++ b/src/Shared/CertificateGeneration/CertificateManager.cs @@ -32,7 +32,7 @@ namespace Microsoft.AspNetCore.Certificates.Generation private const string MacOSSystemKeyChain = "/Library/Keychains/System.keychain"; private static readonly string MacOSUserKeyChain = Environment.GetEnvironmentVariable("HOME") + "/Library/Keychains/login.keychain-db"; private const string MacOSFindCertificateCommandLine = "security"; -#if NETCOREAPP2_0 || NETCOREAPP2_1 || NETCOREAPP2_2 +#if NETCOREAPP2_0 || NETCOREAPP2_1 || NETCOREAPP2_2 || NETCOREAPP3_0 private static readonly string MacOSFindCertificateCommandLineArgumentsFormat = "find-certificate -c {0} -a -Z -p " + MacOSSystemKeyChain; #endif private const string MacOSFindCertificateOutputRegex = "SHA-1 hash: ([0-9A-Z]+)"; @@ -41,7 +41,7 @@ namespace Microsoft.AspNetCore.Certificates.Generation private const string MacOSDeleteCertificateCommandLine = "sudo"; private const string MacOSDeleteCertificateCommandLineArgumentsFormat = "security delete-certificate -Z {0} {1}"; private const string MacOSTrustCertificateCommandLine = "sudo"; -#if NETCOREAPP2_0 || NETCOREAPP2_1 || NETCOREAPP2_2 +#if NETCOREAPP2_0 || NETCOREAPP2_1 || NETCOREAPP2_2 || NETCOREAPP3_0 private static readonly string MacOSTrustCertificateCommandLineArguments = "security add-trusted-cert -d -r trustRoot -k " + MacOSSystemKeyChain + " "; #endif private const int UserCancelledErrorCode = 1223; @@ -153,7 +153,7 @@ namespace Microsoft.AspNetCore.Certificates.Generation } } -#if NETCOREAPP2_0 || NETCOREAPP2_1 || NETCOREAPP2_2 +#if NETCOREAPP2_0 || NETCOREAPP2_1 || NETCOREAPP2_2 || NETCOREAPP3_0 public X509Certificate2 CreateAspNetCoreHttpsDevelopmentCertificate(DateTimeOffset notBefore, DateTimeOffset notAfter, string subjectOverride, DiagnosticInformation diagnostics = null) { @@ -948,4 +948,4 @@ namespace Microsoft.AspNetCore.Certificates.Generation } } } -} \ No newline at end of file +} diff --git a/src/Shared/CertificateGeneration/EnsureCertificateResult.cs b/src/Shared/CertificateGeneration/EnsureCertificateResult.cs index 84c495249d..2106297fae 100644 --- a/src/Shared/CertificateGeneration/EnsureCertificateResult.cs +++ b/src/Shared/CertificateGeneration/EnsureCertificateResult.cs @@ -1,7 +1,7 @@ // 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. -#if NETCOREAPP2_0 || NETCOREAPP2_1 || NETCOREAPP2_2 +#if NETCOREAPP2_0 || NETCOREAPP2_1 || NETCOREAPP2_2 || NETCOREAPP3_0 namespace Microsoft.AspNetCore.Certificates.Generation { @@ -17,4 +17,4 @@ namespace Microsoft.AspNetCore.Certificates.Generation } } -#endif \ No newline at end of file +#endif diff --git a/src/Shared/test/Shared.Tests/CertificateManagerTests.cs b/src/Shared/test/Shared.Tests/CertificateManagerTests.cs index cd314383c9..0979eba21b 100644 --- a/src/Shared/test/Shared.Tests/CertificateManagerTests.cs +++ b/src/Shared/test/Shared.Tests/CertificateManagerTests.cs @@ -1,7 +1,7 @@ // 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. -#if NETCOREAPP2_2 +#if NETCOREAPP3_0 using System; using System.IO; @@ -285,4 +285,4 @@ namespace Microsoft.AspNetCore.Certificates.Generation.Tests } } -#endif \ No newline at end of file +#endif diff --git a/src/Shared/test/Shared.Tests/DotNetMuxerTests.cs b/src/Shared/test/Shared.Tests/DotNetMuxerTests.cs index 92e06a8f70..2f412e292e 100644 --- a/src/Shared/test/Shared.Tests/DotNetMuxerTests.cs +++ b/src/Shared/test/Shared.Tests/DotNetMuxerTests.cs @@ -1,7 +1,7 @@ // 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. -#if NETCOREAPP2_2 +#if NETCOREAPP3_0 using System.IO; using System.Runtime.InteropServices; using Xunit; diff --git a/src/Testing/test/ConditionalFactTest.cs b/src/Testing/test/ConditionalFactTest.cs index a04eb1731d..e136e78247 100644 --- a/src/Testing/test/ConditionalFactTest.cs +++ b/src/Testing/test/ConditionalFactTest.cs @@ -29,7 +29,7 @@ namespace Microsoft.AspNetCore.Testing Assert.True(false, "This test should always be skipped."); } -#if NETCOREAPP2_2 +#if NETCOREAPP3_0 [ConditionalFact] [FrameworkSkipCondition(RuntimeFrameworks.CLR)] public void ThisTestMustRunOnCoreCLR() @@ -57,4 +57,4 @@ namespace Microsoft.AspNetCore.Testing } } } -} \ No newline at end of file +} diff --git a/src/Testing/test/ConditionalTheoryTest.cs b/src/Testing/test/ConditionalTheoryTest.cs index 1181f1365a..6950dd412f 100644 --- a/src/Testing/test/ConditionalTheoryTest.cs +++ b/src/Testing/test/ConditionalTheoryTest.cs @@ -81,7 +81,7 @@ namespace Microsoft.AspNetCore.Testing Assert.True(true); } -#if NETCOREAPP2_2 +#if NETCOREAPP3_0 [ConditionalTheory] [FrameworkSkipCondition(RuntimeFrameworks.CLR)] [MemberData(nameof(GetInts))] From 5261569b614ad389dc9c5c9740728e9aa94403e7 Mon Sep 17 00:00:00 2001 From: Nate McMaster Date: Fri, 2 Nov 2018 00:22:14 -0700 Subject: [PATCH 0005/1101] Reorganize source code in preparation to move into aspnet/Extensions Prior to reorganization, this source code was found in https://github.com/aspnet/DependencyInjection/tree/dotnet/extensions@7a283947c231b6585c8ac95e653950b660f3da96 \n\nCommit migrated from https://github.com/dotnet/extensions/commit/689c4a891cc0783d6c7ed1f9bab1e9854981e65b --- .../ActivatorUtilities/ActivatorUtilities.cs | 429 ++++++++++++++++++ .../ActivatorUtilitiesConstructorAttribute.cs | 26 ++ .../ActivatorUtilities/ObjectFactory.cs | 25 + .../ActivatorUtilities/sharedsources.props | 8 + .../ParameterDefaultValue.cs | 47 ++ 5 files changed, 535 insertions(+) create mode 100644 src/Shared/ActivatorUtilities/ActivatorUtilities.cs create mode 100644 src/Shared/ActivatorUtilities/ActivatorUtilitiesConstructorAttribute.cs create mode 100644 src/Shared/ActivatorUtilities/ObjectFactory.cs create mode 100644 src/Shared/ActivatorUtilities/sharedsources.props create mode 100644 src/Shared/ParameterDefaultValue/ParameterDefaultValue.cs diff --git a/src/Shared/ActivatorUtilities/ActivatorUtilities.cs b/src/Shared/ActivatorUtilities/ActivatorUtilities.cs new file mode 100644 index 0000000000..e2553ced1a --- /dev/null +++ b/src/Shared/ActivatorUtilities/ActivatorUtilities.cs @@ -0,0 +1,429 @@ +// 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.Linq; +using System.Linq.Expressions; +using System.Reflection; +using System.Runtime.ExceptionServices; + +#if ActivatorUtilities_In_DependencyInjection +using Microsoft.Extensions.Internal; + +namespace Microsoft.Extensions.DependencyInjection +#else +namespace Microsoft.Extensions.Internal +#endif +{ + /// + /// Helper code for the various activator services. + /// + +#if ActivatorUtilities_In_DependencyInjection + public +#else + // Do not take a dependency on this class unless you are explicitly trying to avoid taking a + // dependency on Microsoft.AspNetCore.DependencyInjection.Abstractions. + internal +#endif + static class ActivatorUtilities + { + private static readonly MethodInfo GetServiceInfo = + GetMethodInfo>((sp, t, r, c) => GetService(sp, t, r, c)); + + /// + /// Instantiate a type with constructor arguments provided directly and/or from an . + /// + /// The service provider used to resolve dependencies + /// The type to activate + /// Constructor arguments not provided by the . + /// An activated object of type instanceType + public static object CreateInstance(IServiceProvider provider, Type instanceType, params object[] parameters) + { + int bestLength = -1; + var seenPreferred = false; + + ConstructorMatcher bestMatcher = null; + + if (!instanceType.GetTypeInfo().IsAbstract) + { + foreach (var constructor in instanceType + .GetTypeInfo() + .DeclaredConstructors + .Where(c => !c.IsStatic && c.IsPublic)) + { + var matcher = new ConstructorMatcher(constructor); + var isPreferred = constructor.IsDefined(typeof(ActivatorUtilitiesConstructorAttribute), false); + var length = matcher.Match(parameters); + + if (isPreferred) + { + if (seenPreferred) + { + ThrowMultipleCtorsMarkedWithAttributeException(); + } + + if (length == -1) + { + ThrowMarkedCtorDoesNotTakeAllProvidedArguments(); + } + } + + if (isPreferred || bestLength < length) + { + bestLength = length; + bestMatcher = matcher; + } + + seenPreferred |= isPreferred; + } + } + + if (bestMatcher == null) + { + var message = $"A suitable constructor for type '{instanceType}' could not be located. Ensure the type is concrete and services are registered for all parameters of a public constructor."; + throw new InvalidOperationException(message); + } + + return bestMatcher.CreateInstance(provider); + } + + /// + /// Create a delegate that will instantiate a type with constructor arguments provided directly + /// and/or from an . + /// + /// The type to activate + /// + /// The types of objects, in order, that will be passed to the returned function as its second parameter + /// + /// + /// A factory that will instantiate instanceType using an + /// and an argument array containing objects matching the types defined in argumentTypes + /// + public static ObjectFactory CreateFactory(Type instanceType, Type[] argumentTypes) + { + FindApplicableConstructor(instanceType, argumentTypes, out ConstructorInfo constructor, out int?[] parameterMap); + + var provider = Expression.Parameter(typeof(IServiceProvider), "provider"); + var argumentArray = Expression.Parameter(typeof(object[]), "argumentArray"); + var factoryExpressionBody = BuildFactoryExpression(constructor, parameterMap, provider, argumentArray); + + var factoryLamda = Expression.Lambda>( + factoryExpressionBody, provider, argumentArray); + + var result = factoryLamda.Compile(); + return result.Invoke; + } + + /// + /// Instantiate a type with constructor arguments provided directly and/or from an . + /// + /// The type to activate + /// The service provider used to resolve dependencies + /// Constructor arguments not provided by the . + /// An activated object of type T + public static T CreateInstance(IServiceProvider provider, params object[] parameters) + { + return (T)CreateInstance(provider, typeof(T), parameters); + } + + + /// + /// Retrieve an instance of the given type from the service provider. If one is not found then instantiate it directly. + /// + /// The type of the service + /// The service provider used to resolve dependencies + /// The resolved service or created instance + public static T GetServiceOrCreateInstance(IServiceProvider provider) + { + return (T)GetServiceOrCreateInstance(provider, typeof(T)); + } + + /// + /// Retrieve an instance of the given type from the service provider. If one is not found then instantiate it directly. + /// + /// The service provider + /// The type of the service + /// The resolved service or created instance + public static object GetServiceOrCreateInstance(IServiceProvider provider, Type type) + { + return provider.GetService(type) ?? CreateInstance(provider, type); + } + + private static MethodInfo GetMethodInfo(Expression expr) + { + var mc = (MethodCallExpression)expr.Body; + return mc.Method; + } + + private static object GetService(IServiceProvider sp, Type type, Type requiredBy, bool isDefaultParameterRequired) + { + var service = sp.GetService(type); + if (service == null && !isDefaultParameterRequired) + { + var message = $"Unable to resolve service for type '{type}' while attempting to activate '{requiredBy}'."; + throw new InvalidOperationException(message); + } + return service; + } + + private static Expression BuildFactoryExpression( + ConstructorInfo constructor, + int?[] parameterMap, + Expression serviceProvider, + Expression factoryArgumentArray) + { + var constructorParameters = constructor.GetParameters(); + var constructorArguments = new Expression[constructorParameters.Length]; + + for (var i = 0; i < constructorParameters.Length; i++) + { + var constructorParameter = constructorParameters[i]; + var parameterType = constructorParameter.ParameterType; + var hasDefaultValue = ParameterDefaultValue.TryGetDefaultValue(constructorParameter, out var defaultValue); + + if (parameterMap[i] != null) + { + constructorArguments[i] = Expression.ArrayAccess(factoryArgumentArray, Expression.Constant(parameterMap[i])); + } + else + { + var parameterTypeExpression = new Expression[] { serviceProvider, + Expression.Constant(parameterType, typeof(Type)), + Expression.Constant(constructor.DeclaringType, typeof(Type)), + Expression.Constant(hasDefaultValue) }; + constructorArguments[i] = Expression.Call(GetServiceInfo, parameterTypeExpression); + } + + // Support optional constructor arguments by passing in the default value + // when the argument would otherwise be null. + if (hasDefaultValue) + { + var defaultValueExpression = Expression.Constant(defaultValue); + constructorArguments[i] = Expression.Coalesce(constructorArguments[i], defaultValueExpression); + } + + constructorArguments[i] = Expression.Convert(constructorArguments[i], parameterType); + } + + return Expression.New(constructor, constructorArguments); + } + + private static void FindApplicableConstructor( + Type instanceType, + Type[] argumentTypes, + out ConstructorInfo matchingConstructor, + out int?[] parameterMap) + { + matchingConstructor = null; + parameterMap = null; + + if (!TryFindPreferredConstructor(instanceType, argumentTypes, ref matchingConstructor, ref parameterMap) && + !TryFindMatchingConstructor(instanceType, argumentTypes, ref matchingConstructor, ref parameterMap)) + { + var message = $"A suitable constructor for type '{instanceType}' could not be located. Ensure the type is concrete and services are registered for all parameters of a public constructor."; + throw new InvalidOperationException(message); + } + } + + // Tries to find constructor based on provided argument types + private static bool TryFindMatchingConstructor( + Type instanceType, + Type[] argumentTypes, + ref ConstructorInfo matchingConstructor, + ref int?[] parameterMap) + { + foreach (var constructor in instanceType.GetTypeInfo().DeclaredConstructors) + { + if (constructor.IsStatic || !constructor.IsPublic) + { + continue; + } + + if (TryCreateParameterMap(constructor.GetParameters(), argumentTypes, out int?[] tempParameterMap)) + { + if (matchingConstructor != null) + { + throw new InvalidOperationException($"Multiple constructors accepting all given argument types have been found in type '{instanceType}'. There should only be one applicable constructor."); + } + + matchingConstructor = constructor; + parameterMap = tempParameterMap; + } + } + + return matchingConstructor != null; + } + + // Tries to find constructor marked with ActivatorUtilitiesConstructorAttribute + private static bool TryFindPreferredConstructor( + Type instanceType, + Type[] argumentTypes, + ref ConstructorInfo matchingConstructor, + ref int?[] parameterMap) + { + var seenPreferred = false; + foreach (var constructor in instanceType.GetTypeInfo().DeclaredConstructors) + { + if (constructor.IsStatic || !constructor.IsPublic) + { + continue; + } + + if (constructor.IsDefined(typeof(ActivatorUtilitiesConstructorAttribute), false)) + { + if (seenPreferred) + { + ThrowMultipleCtorsMarkedWithAttributeException(); + } + + if (!TryCreateParameterMap(constructor.GetParameters(), argumentTypes, out int?[] tempParameterMap)) + { + ThrowMarkedCtorDoesNotTakeAllProvidedArguments(); + } + + matchingConstructor = constructor; + parameterMap = tempParameterMap; + seenPreferred = true; + } + } + + return matchingConstructor != null; + } + + // Creates an injective parameterMap from givenParameterTypes to assignable constructorParameters. + // Returns true if each given parameter type is assignable to a unique; otherwise, false. + private static bool TryCreateParameterMap(ParameterInfo[] constructorParameters, Type[] argumentTypes, out int?[] parameterMap) + { + parameterMap = new int?[constructorParameters.Length]; + + for (var i = 0; i < argumentTypes.Length; i++) + { + var foundMatch = false; + var givenParameter = argumentTypes[i].GetTypeInfo(); + + for (var j = 0; j < constructorParameters.Length; j++) + { + if (parameterMap[j] != null) + { + // This ctor parameter has already been matched + continue; + } + + if (constructorParameters[j].ParameterType.GetTypeInfo().IsAssignableFrom(givenParameter)) + { + foundMatch = true; + parameterMap[j] = i; + break; + } + } + + if (!foundMatch) + { + return false; + } + } + + return true; + } + + private class ConstructorMatcher + { + private readonly ConstructorInfo _constructor; + private readonly ParameterInfo[] _parameters; + private readonly object[] _parameterValues; + private readonly bool[] _parameterValuesSet; + + public ConstructorMatcher(ConstructorInfo constructor) + { + _constructor = constructor; + _parameters = _constructor.GetParameters(); + _parameterValuesSet = new bool[_parameters.Length]; + _parameterValues = new object[_parameters.Length]; + } + + public int Match(object[] givenParameters) + { + var applyIndexStart = 0; + var applyExactLength = 0; + for (var givenIndex = 0; givenIndex != givenParameters.Length; givenIndex++) + { + var givenType = givenParameters[givenIndex]?.GetType().GetTypeInfo(); + var givenMatched = false; + + for (var applyIndex = applyIndexStart; givenMatched == false && applyIndex != _parameters.Length; ++applyIndex) + { + if (_parameterValuesSet[applyIndex] == false && + _parameters[applyIndex].ParameterType.GetTypeInfo().IsAssignableFrom(givenType)) + { + givenMatched = true; + _parameterValuesSet[applyIndex] = true; + _parameterValues[applyIndex] = givenParameters[givenIndex]; + if (applyIndexStart == applyIndex) + { + applyIndexStart++; + if (applyIndex == givenIndex) + { + applyExactLength = applyIndex; + } + } + } + } + + if (givenMatched == false) + { + return -1; + } + } + return applyExactLength; + } + + public object CreateInstance(IServiceProvider provider) + { + for (var index = 0; index != _parameters.Length; index++) + { + if (_parameterValuesSet[index] == false) + { + var value = provider.GetService(_parameters[index].ParameterType); + if (value == null) + { + if (!ParameterDefaultValue.TryGetDefaultValue(_parameters[index], out var defaultValue)) + { + throw new InvalidOperationException($"Unable to resolve service for type '{_parameters[index].ParameterType}' while attempting to activate '{_constructor.DeclaringType}'."); + } + else + { + _parameterValues[index] = defaultValue; + } + } + else + { + _parameterValues[index] = value; + } + } + } + + try + { + return _constructor.Invoke(_parameterValues); + } + catch (TargetInvocationException ex) + { + ExceptionDispatchInfo.Capture(ex.InnerException).Throw(); + // The above line will always throw, but the compiler requires we throw explicitly. + throw; + } + } + } + + private static void ThrowMultipleCtorsMarkedWithAttributeException() + { + throw new InvalidOperationException($"Multiple constructors were marked with {nameof(ActivatorUtilitiesConstructorAttribute)}."); + } + + private static void ThrowMarkedCtorDoesNotTakeAllProvidedArguments() + { + throw new InvalidOperationException($"Constructor marked with {nameof(ActivatorUtilitiesConstructorAttribute)} does not accept all given argument types."); + } + } +} diff --git a/src/Shared/ActivatorUtilities/ActivatorUtilitiesConstructorAttribute.cs b/src/Shared/ActivatorUtilities/ActivatorUtilitiesConstructorAttribute.cs new file mode 100644 index 0000000000..67ffa13f6f --- /dev/null +++ b/src/Shared/ActivatorUtilities/ActivatorUtilitiesConstructorAttribute.cs @@ -0,0 +1,26 @@ +// 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; + +#if ActivatorUtilities_In_DependencyInjection +namespace Microsoft.Extensions.DependencyInjection +#else +namespace Microsoft.Extensions.Internal +#endif +{ + /// + /// Marks the constructor to be used when activating type using . + /// + +#if ActivatorUtilities_In_DependencyInjection + public +#else + // Do not take a dependency on this class unless you are explicitly trying to avoid taking a + // dependency on Microsoft.AspNetCore.DependencyInjection.Abstractions. + internal +#endif + class ActivatorUtilitiesConstructorAttribute: Attribute + { + } +} diff --git a/src/Shared/ActivatorUtilities/ObjectFactory.cs b/src/Shared/ActivatorUtilities/ObjectFactory.cs new file mode 100644 index 0000000000..517247811e --- /dev/null +++ b/src/Shared/ActivatorUtilities/ObjectFactory.cs @@ -0,0 +1,25 @@ +// 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; + +#if ActivatorUtilities_In_DependencyInjection +namespace Microsoft.Extensions.DependencyInjection +#else +namespace Microsoft.Extensions.Internal +#endif +{ + + /// + /// The result of . + /// + /// The to get service arguments from. + /// Additional constructor arguments. + /// The instantiated type. +#if ActivatorUtilities_In_DependencyInjection + public +#else + internal +#endif + delegate object ObjectFactory(IServiceProvider serviceProvider, object[] arguments); +} \ No newline at end of file diff --git a/src/Shared/ActivatorUtilities/sharedsources.props b/src/Shared/ActivatorUtilities/sharedsources.props new file mode 100644 index 0000000000..b35fe34b10 --- /dev/null +++ b/src/Shared/ActivatorUtilities/sharedsources.props @@ -0,0 +1,8 @@ + + + + true + $(ContentTargetFolders)\cs\netstandard1.0\ + + + diff --git a/src/Shared/ParameterDefaultValue/ParameterDefaultValue.cs b/src/Shared/ParameterDefaultValue/ParameterDefaultValue.cs new file mode 100644 index 0000000000..a71bad37b1 --- /dev/null +++ b/src/Shared/ParameterDefaultValue/ParameterDefaultValue.cs @@ -0,0 +1,47 @@ +// 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.Reflection; + +namespace Microsoft.Extensions.Internal +{ + internal class ParameterDefaultValue + { + public static bool TryGetDefaultValue(ParameterInfo parameter, out object defaultValue) + { + bool hasDefaultValue; + var tryToGetDefaultValue = true; + defaultValue = null; + + try + { + hasDefaultValue = parameter.HasDefaultValue; + } + catch (FormatException) when (parameter.ParameterType == typeof(DateTime)) + { + // Workaround for https://github.com/dotnet/corefx/issues/12338 + // If HasDefaultValue throws FormatException for DateTime + // we expect it to have default value + hasDefaultValue = true; + tryToGetDefaultValue = false; + } + + if (hasDefaultValue) + { + if (tryToGetDefaultValue) + { + defaultValue = parameter.DefaultValue; + } + + // Workaround for https://github.com/dotnet/corefx/issues/11797 + if (defaultValue == null && parameter.ParameterType.IsValueType) + { + defaultValue = Activator.CreateInstance(parameter.ParameterType); + } + } + + return hasDefaultValue; + } + } +} From 574a034ddd13f71c06586615b3a5b57884bb2afc Mon Sep 17 00:00:00 2001 From: Nate McMaster Date: Fri, 2 Nov 2018 03:25:45 -0700 Subject: [PATCH 0006/1101] Reorganize source code in preparation to move into aspnet/Extensions Prior to reorganization, this source code was found in https://github.com/aspnet/FileSystem/tree/dotnet/extensions@baebb8b0c672ab37bac72d7196da1b919d362cc5 \n\nCommit migrated from https://github.com/dotnet/extensions/commit/c087cadf1dfdbd2b8785ef764e5ef58a1a7e5ed0 --- src/FileProviders/Directory.Build.props | 8 + .../Embedded/src/EmbeddedFileProvider.cs | 181 ++++++++ .../Embedded/src/EmbeddedResourceFileInfo.cs | 94 ++++ .../src/EnumerableDirectoryContents.cs | 39 ++ .../src/Manifest/EmbeddedFilesManifest.cs | 91 ++++ .../src/Manifest/ManifestDirectory.cs | 127 ++++++ .../src/Manifest/ManifestDirectoryContents.cs | 72 +++ .../src/Manifest/ManifestDirectoryInfo.cs | 39 ++ .../Embedded/src/Manifest/ManifestEntry.cs | 34 ++ .../Embedded/src/Manifest/ManifestFile.cs | 31 ++ .../Embedded/src/Manifest/ManifestFileInfo.cs | 71 +++ .../Embedded/src/Manifest/ManifestParser.cs | 159 +++++++ .../src/Manifest/ManifestRootDirectory.cs | 16 + .../src/Manifest/ManifestSinkDirectory.cs | 22 + .../src/ManifestEmbeddedFileProvider.cs | 153 +++++++ ...t.Extensions.FileProviders.Embedded.csproj | 46 ++ ...t.Extensions.FileProviders.Embedded.nuspec | 33 ++ .../Embedded/src/Properties/AssemblyInfo.cs | 4 + .../Embedded/src/baseline.netcore.json | 343 ++++++++++++++ ...ft.Extensions.FileProviders.Embedded.props | 17 + ....Extensions.FileProviders.Embedded.targets | 69 +++ ...ft.Extensions.FileProviders.Embedded.props | 3 + ....Extensions.FileProviders.Embedded.targets | 3 + .../test/EmbeddedFileProviderTests.cs | 231 ++++++++++ src/FileProviders/Embedded/test/File.txt | 1 + .../Embedded/test/FileInfoComparer.cs | 34 ++ .../Manifest/EmbeddedFilesManifestTests.cs | 58 +++ .../test/Manifest/ManifestEntryTests.cs | 113 +++++ .../test/Manifest/ManifestParserTests.cs | 116 +++++ .../Embedded/test/Manifest/TestEntry.cs | 41 ++ .../test/ManifestEmbeddedFileProviderTests.cs | 428 ++++++++++++++++++ ...nsions.FileProviders.Embedded.Tests.csproj | 15 + .../Embedded/test/Resources/File.txt | 1 + .../ResourcesInSubdirectory/File3.txt | 1 + .../Embedded/test/TestAssembly.cs | 69 +++ .../Embedded/test/TestFileInfo.cs | 34 ++ src/FileProviders/Embedded/test/sub/File2.txt | 1 + .../Embedded/test/sub/dir/File3.txt | Bin 0 -> 6 bytes .../Manifest.MSBuildTask/src/EmbeddedItem.cs | 21 + .../Manifest.MSBuildTask/src/Entry.cs | 120 +++++ .../src/GenerateEmbeddedResourcesManifest.cs | 104 +++++ .../Manifest.MSBuildTask/src/Manifest.cs | 85 ++++ ...ileProviders.Embedded.Manifest.Task.csproj | 25 + .../GenerateEmbeddedResourcesManifestTest.cs | 388 ++++++++++++++++ ...oviders.Embedded.Manifest.Task.Test.csproj | 11 + .../test/SetExtensions.cs | 20 + 46 files changed, 3572 insertions(+) create mode 100644 src/FileProviders/Directory.Build.props create mode 100644 src/FileProviders/Embedded/src/EmbeddedFileProvider.cs create mode 100644 src/FileProviders/Embedded/src/EmbeddedResourceFileInfo.cs create mode 100644 src/FileProviders/Embedded/src/EnumerableDirectoryContents.cs create mode 100644 src/FileProviders/Embedded/src/Manifest/EmbeddedFilesManifest.cs create mode 100644 src/FileProviders/Embedded/src/Manifest/ManifestDirectory.cs create mode 100644 src/FileProviders/Embedded/src/Manifest/ManifestDirectoryContents.cs create mode 100644 src/FileProviders/Embedded/src/Manifest/ManifestDirectoryInfo.cs create mode 100644 src/FileProviders/Embedded/src/Manifest/ManifestEntry.cs create mode 100644 src/FileProviders/Embedded/src/Manifest/ManifestFile.cs create mode 100644 src/FileProviders/Embedded/src/Manifest/ManifestFileInfo.cs create mode 100644 src/FileProviders/Embedded/src/Manifest/ManifestParser.cs create mode 100644 src/FileProviders/Embedded/src/Manifest/ManifestRootDirectory.cs create mode 100644 src/FileProviders/Embedded/src/Manifest/ManifestSinkDirectory.cs create mode 100644 src/FileProviders/Embedded/src/ManifestEmbeddedFileProvider.cs create mode 100644 src/FileProviders/Embedded/src/Microsoft.Extensions.FileProviders.Embedded.csproj create mode 100644 src/FileProviders/Embedded/src/Microsoft.Extensions.FileProviders.Embedded.nuspec create mode 100644 src/FileProviders/Embedded/src/Properties/AssemblyInfo.cs create mode 100644 src/FileProviders/Embedded/src/baseline.netcore.json create mode 100644 src/FileProviders/Embedded/src/build/netstandard2.0/Microsoft.Extensions.FileProviders.Embedded.props create mode 100644 src/FileProviders/Embedded/src/build/netstandard2.0/Microsoft.Extensions.FileProviders.Embedded.targets create mode 100644 src/FileProviders/Embedded/src/buildMultiTargeting/Microsoft.Extensions.FileProviders.Embedded.props create mode 100644 src/FileProviders/Embedded/src/buildMultiTargeting/Microsoft.Extensions.FileProviders.Embedded.targets create mode 100644 src/FileProviders/Embedded/test/EmbeddedFileProviderTests.cs create mode 100644 src/FileProviders/Embedded/test/File.txt create mode 100644 src/FileProviders/Embedded/test/FileInfoComparer.cs create mode 100644 src/FileProviders/Embedded/test/Manifest/EmbeddedFilesManifestTests.cs create mode 100644 src/FileProviders/Embedded/test/Manifest/ManifestEntryTests.cs create mode 100644 src/FileProviders/Embedded/test/Manifest/ManifestParserTests.cs create mode 100644 src/FileProviders/Embedded/test/Manifest/TestEntry.cs create mode 100644 src/FileProviders/Embedded/test/ManifestEmbeddedFileProviderTests.cs create mode 100644 src/FileProviders/Embedded/test/Microsoft.Extensions.FileProviders.Embedded.Tests.csproj create mode 100644 src/FileProviders/Embedded/test/Resources/File.txt create mode 100644 src/FileProviders/Embedded/test/Resources/ResourcesInSubdirectory/File3.txt create mode 100644 src/FileProviders/Embedded/test/TestAssembly.cs create mode 100644 src/FileProviders/Embedded/test/TestFileInfo.cs create mode 100644 src/FileProviders/Embedded/test/sub/File2.txt create mode 100644 src/FileProviders/Embedded/test/sub/dir/File3.txt create mode 100644 src/FileProviders/Manifest.MSBuildTask/src/EmbeddedItem.cs create mode 100644 src/FileProviders/Manifest.MSBuildTask/src/Entry.cs create mode 100644 src/FileProviders/Manifest.MSBuildTask/src/GenerateEmbeddedResourcesManifest.cs create mode 100644 src/FileProviders/Manifest.MSBuildTask/src/Manifest.cs create mode 100644 src/FileProviders/Manifest.MSBuildTask/src/Microsoft.Extensions.FileProviders.Embedded.Manifest.Task.csproj create mode 100644 src/FileProviders/Manifest.MSBuildTask/test/GenerateEmbeddedResourcesManifestTest.cs create mode 100644 src/FileProviders/Manifest.MSBuildTask/test/Microsoft.Extensions.FileProviders.Embedded.Manifest.Task.Test.csproj create mode 100644 src/FileProviders/Manifest.MSBuildTask/test/SetExtensions.cs diff --git a/src/FileProviders/Directory.Build.props b/src/FileProviders/Directory.Build.props new file mode 100644 index 0000000000..bf4410dcb7 --- /dev/null +++ b/src/FileProviders/Directory.Build.props @@ -0,0 +1,8 @@ + + + + + true + files;filesystem + + diff --git a/src/FileProviders/Embedded/src/EmbeddedFileProvider.cs b/src/FileProviders/Embedded/src/EmbeddedFileProvider.cs new file mode 100644 index 0000000000..75f3f49e49 --- /dev/null +++ b/src/FileProviders/Embedded/src/EmbeddedFileProvider.cs @@ -0,0 +1,181 @@ +// 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.Collections.Generic; +using System.IO; +using System.Linq; +using System.Reflection; +using System.Text; +using Microsoft.Extensions.FileProviders.Embedded; +using Microsoft.Extensions.Primitives; + +namespace Microsoft.Extensions.FileProviders +{ + /// + /// Looks up files using embedded resources in the specified assembly. + /// This file provider is case sensitive. + /// + public class EmbeddedFileProvider : IFileProvider + { + private static readonly char[] _invalidFileNameChars = Path.GetInvalidFileNameChars() + .Where(c => c != '/' && c != '\\').ToArray(); + + private readonly Assembly _assembly; + private readonly string _baseNamespace; + private readonly DateTimeOffset _lastModified; + + /// + /// Initializes a new instance of the class using the specified + /// assembly with the base namespace defaulting to the assembly name. + /// + /// The assembly that contains the embedded resources. + public EmbeddedFileProvider(Assembly assembly) + : this(assembly, assembly?.GetName()?.Name) + { + } + + /// + /// Initializes a new instance of the class using the specified + /// assembly and base namespace. + /// + /// The assembly that contains the embedded resources. + /// The base namespace that contains the embedded resources. + public EmbeddedFileProvider(Assembly assembly, string baseNamespace) + { + if (assembly == null) + { + throw new ArgumentNullException("assembly"); + } + + _baseNamespace = string.IsNullOrEmpty(baseNamespace) ? string.Empty : baseNamespace + "."; + _assembly = assembly; + + _lastModified = DateTimeOffset.UtcNow; + + if (!string.IsNullOrEmpty(_assembly.Location)) + { + try + { + _lastModified = File.GetLastWriteTimeUtc(_assembly.Location); + } + catch (PathTooLongException) + { + } + catch (UnauthorizedAccessException) + { + } + } + } + + /// + /// Locates a file at the given path. + /// + /// The path that identifies the file. + /// + /// The file information. Caller must check Exists property. A if the file could + /// not be found. + /// + public IFileInfo GetFileInfo(string subpath) + { + if (string.IsNullOrEmpty(subpath)) + { + return new NotFoundFileInfo(subpath); + } + + var builder = new StringBuilder(_baseNamespace.Length + subpath.Length); + builder.Append(_baseNamespace); + + // Relative paths starting with a leading slash okay + if (subpath.StartsWith("/", StringComparison.Ordinal)) + { + builder.Append(subpath, 1, subpath.Length - 1); + } + else + { + builder.Append(subpath); + } + + for (var i = _baseNamespace.Length; i < builder.Length; i++) + { + if (builder[i] == '/' || builder[i] == '\\') + { + builder[i] = '.'; + } + } + + var resourcePath = builder.ToString(); + if (HasInvalidPathChars(resourcePath)) + { + return new NotFoundFileInfo(resourcePath); + } + + var name = Path.GetFileName(subpath); + if (_assembly.GetManifestResourceInfo(resourcePath) == null) + { + return new NotFoundFileInfo(name); + } + + return new EmbeddedResourceFileInfo(_assembly, resourcePath, name, _lastModified); + } + + /// + /// Enumerate a directory at the given path, if any. + /// This file provider uses a flat directory structure. Everything under the base namespace is considered to be one + /// directory. + /// + /// The path that identifies the directory + /// + /// Contents of the directory. Caller must check Exists property. A if no + /// resources were found that match + /// + public IDirectoryContents GetDirectoryContents(string subpath) + { + // The file name is assumed to be the remainder of the resource name. + if (subpath == null) + { + return NotFoundDirectoryContents.Singleton; + } + + // EmbeddedFileProvider only supports a flat file structure at the base namespace. + if (subpath.Length != 0 && !string.Equals(subpath, "/", StringComparison.Ordinal)) + { + return NotFoundDirectoryContents.Singleton; + } + + var entries = new List(); + + // TODO: The list of resources in an assembly isn't going to change. Consider caching. + var resources = _assembly.GetManifestResourceNames(); + for (var i = 0; i < resources.Length; i++) + { + var resourceName = resources[i]; + if (resourceName.StartsWith(_baseNamespace, StringComparison.Ordinal)) + { + entries.Add(new EmbeddedResourceFileInfo( + _assembly, + resourceName, + resourceName.Substring(_baseNamespace.Length), + _lastModified)); + } + } + + return new EnumerableDirectoryContents(entries); + } + + /// + /// Embedded files do not change. + /// + /// This parameter is ignored + /// A + public IChangeToken Watch(string pattern) + { + return NullChangeToken.Singleton; + } + + private static bool HasInvalidPathChars(string path) + { + return path.IndexOfAny(_invalidFileNameChars) != -1; + } + } +} diff --git a/src/FileProviders/Embedded/src/EmbeddedResourceFileInfo.cs b/src/FileProviders/Embedded/src/EmbeddedResourceFileInfo.cs new file mode 100644 index 0000000000..5dca527342 --- /dev/null +++ b/src/FileProviders/Embedded/src/EmbeddedResourceFileInfo.cs @@ -0,0 +1,94 @@ +// 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.IO; +using System.Reflection; + +namespace Microsoft.Extensions.FileProviders.Embedded +{ + /// + /// Represents a file embedded in an assembly. + /// + public class EmbeddedResourceFileInfo : IFileInfo + { + private readonly Assembly _assembly; + private readonly string _resourcePath; + + private long? _length; + + /// + /// Initializes a new instance of for an assembly using as the base + /// + /// The assembly that contains the embedded resource + /// The path to the embedded resource + /// An arbitrary name for this instance + /// The to use for + public EmbeddedResourceFileInfo( + Assembly assembly, + string resourcePath, + string name, + DateTimeOffset lastModified) + { + _assembly = assembly; + _resourcePath = resourcePath; + Name = name; + LastModified = lastModified; + } + + /// + /// Always true. + /// + public bool Exists => true; + + /// + /// The length, in bytes, of the embedded resource + /// + public long Length + { + get + { + if (!_length.HasValue) + { + using (var stream = _assembly.GetManifestResourceStream(_resourcePath)) + { + _length = stream.Length; + } + } + return _length.Value; + } + } + + /// + /// Always null. + /// + public string PhysicalPath => null; + + /// + /// The name of embedded file + /// + public string Name { get; } + + /// + /// The time, in UTC, when the was created + /// + public DateTimeOffset LastModified { get; } + + /// + /// Always false. + /// + public bool IsDirectory => false; + + /// + public Stream CreateReadStream() + { + var stream = _assembly.GetManifestResourceStream(_resourcePath); + if (!_length.HasValue) + { + _length = stream.Length; + } + + return stream; + } + } +} diff --git a/src/FileProviders/Embedded/src/EnumerableDirectoryContents.cs b/src/FileProviders/Embedded/src/EnumerableDirectoryContents.cs new file mode 100644 index 0000000000..012723eba6 --- /dev/null +++ b/src/FileProviders/Embedded/src/EnumerableDirectoryContents.cs @@ -0,0 +1,39 @@ +// 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.Collections; +using System.Collections.Generic; + +namespace Microsoft.Extensions.FileProviders.Embedded +{ + internal class EnumerableDirectoryContents : IDirectoryContents + { + private readonly IEnumerable _entries; + + public EnumerableDirectoryContents(IEnumerable entries) + { + if (entries == null) + { + throw new ArgumentNullException(nameof(entries)); + } + + _entries = entries; + } + + public bool Exists + { + get { return true; } + } + + public IEnumerator GetEnumerator() + { + return _entries.GetEnumerator(); + } + + IEnumerator IEnumerable.GetEnumerator() + { + return _entries.GetEnumerator(); + } + } +} \ No newline at end of file diff --git a/src/FileProviders/Embedded/src/Manifest/EmbeddedFilesManifest.cs b/src/FileProviders/Embedded/src/Manifest/EmbeddedFilesManifest.cs new file mode 100644 index 0000000000..f017b9b289 --- /dev/null +++ b/src/FileProviders/Embedded/src/Manifest/EmbeddedFilesManifest.cs @@ -0,0 +1,91 @@ +// 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.Diagnostics; +using System.IO; +using System.Linq; +using Microsoft.Extensions.Primitives; + +namespace Microsoft.Extensions.FileProviders.Embedded.Manifest +{ + internal class EmbeddedFilesManifest + { + private static readonly char[] _invalidFileNameChars = Path.GetInvalidFileNameChars() + .Where(c => c != Path.DirectorySeparatorChar && c != Path.AltDirectorySeparatorChar).ToArray(); + + private static readonly char[] _separators = new char[] { Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar }; + + private readonly ManifestDirectory _rootDirectory; + + internal EmbeddedFilesManifest(ManifestDirectory rootDirectory) + { + if (rootDirectory == null) + { + throw new ArgumentNullException(nameof(rootDirectory)); + } + + _rootDirectory = rootDirectory; + } + + internal ManifestEntry ResolveEntry(string path) + { + if (string.IsNullOrEmpty(path) || HasInvalidPathChars(path)) + { + return null; + } + + // trimmed is a string without leading nor trailing path separators + // so if we find an empty string while iterating over the segments + // we know for sure the path is invalid and we treat it as the above + // case by returning null. + // Examples of invalid paths are: //wwwroot /\wwwroot //wwwroot//jquery.js + var trimmed = RemoveLeadingAndTrailingDirectorySeparators(path); + // Paths consisting only of a single path separator like / or \ are ok. + if (trimmed.Length == 0) + { + return _rootDirectory; + } + + var tokenizer = new StringTokenizer(trimmed, _separators); + ManifestEntry currentEntry = _rootDirectory; + foreach (var segment in tokenizer) + { + if (segment.Equals("")) + { + return null; + } + + currentEntry = currentEntry.Traverse(segment); + } + + return currentEntry; + } + + private static StringSegment RemoveLeadingAndTrailingDirectorySeparators(string path) + { + Debug.Assert(path.Length > 0); + var start = Array.IndexOf(_separators, path[0]) == -1 ? 0 : 1; + if (start == path.Length) + { + return StringSegment.Empty; + } + + var end = Array.IndexOf(_separators, path[path.Length - 1]) == -1 ? path.Length : path.Length - 1; + var trimmed = new StringSegment(path, start, end - start); + return trimmed; + } + + internal EmbeddedFilesManifest Scope(string path) + { + if (ResolveEntry(path) is ManifestDirectory directory && directory != ManifestEntry.UnknownPath) + { + return new EmbeddedFilesManifest(directory.ToRootDirectory()); + } + + throw new InvalidOperationException($"Invalid path: '{path}'"); + } + + private static bool HasInvalidPathChars(string path) => path.IndexOfAny(_invalidFileNameChars) != -1; + } +} diff --git a/src/FileProviders/Embedded/src/Manifest/ManifestDirectory.cs b/src/FileProviders/Embedded/src/Manifest/ManifestDirectory.cs new file mode 100644 index 0000000000..b75653a0fb --- /dev/null +++ b/src/FileProviders/Embedded/src/Manifest/ManifestDirectory.cs @@ -0,0 +1,127 @@ +// 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.Collections.Generic; +using Microsoft.Extensions.Primitives; + +namespace Microsoft.Extensions.FileProviders.Embedded.Manifest +{ + internal class ManifestDirectory : ManifestEntry + { + protected ManifestDirectory(string name, ManifestEntry[] children) + : base(name) + { + if (children == null) + { + throw new ArgumentNullException(nameof(children)); + } + + Children = children; + } + + public IReadOnlyList Children { get; protected set; } + + public override ManifestEntry Traverse(StringSegment segment) + { + if (segment.Equals(".", StringComparison.Ordinal)) + { + return this; + } + + if (segment.Equals("..", StringComparison.Ordinal)) + { + return Parent; + } + + foreach (var child in Children) + { + if (segment.Equals(child.Name, StringComparison.OrdinalIgnoreCase)) + { + return child; + } + } + + return UnknownPath; + } + + public virtual ManifestDirectory ToRootDirectory() => CreateRootDirectory(CopyChildren()); + + public static ManifestDirectory CreateDirectory(string name, ManifestEntry[] children) + { + if (string.IsNullOrWhiteSpace(name)) + { + throw new ArgumentException($"'{nameof(name)}' must not be null, empty or whitespace.", nameof(name)); + } + + if (children == null) + { + throw new ArgumentNullException(nameof(children)); + } + + var result = new ManifestDirectory(name, children); + ValidateChildrenAndSetParent(children, result); + + return result; + } + + public static ManifestRootDirectory CreateRootDirectory(ManifestEntry[] children) + { + if (children == null) + { + throw new ArgumentNullException(nameof(children)); + } + + var result = new ManifestRootDirectory(children); + ValidateChildrenAndSetParent(children, result); + + return result; + } + + internal static void ValidateChildrenAndSetParent(ManifestEntry[] children, ManifestDirectory parent) + { + foreach (var child in children) + { + if (child == UnknownPath) + { + throw new InvalidOperationException($"Invalid entry type '{nameof(ManifestSinkDirectory)}'"); + } + + if (child is ManifestRootDirectory) + { + throw new InvalidOperationException($"Can't add a root folder as a child"); + } + + child.SetParent(parent); + } + } + + private ManifestEntry[] CopyChildren() + { + var list = new List(); + for (int i = 0; i < Children.Count; i++) + { + var child = Children[i]; + switch (child) + { + case ManifestSinkDirectory s: + case ManifestRootDirectory r: + throw new InvalidOperationException("Unexpected manifest node."); + case ManifestDirectory d: + var grandChildren = d.CopyChildren(); + var newDirectory = CreateDirectory(d.Name, grandChildren); + list.Add(newDirectory); + break; + case ManifestFile f: + var file = new ManifestFile(f.Name, f.ResourcePath); + list.Add(file); + break; + default: + throw new InvalidOperationException("Unexpected manifest node."); + } + } + + return list.ToArray(); + } + } +} diff --git a/src/FileProviders/Embedded/src/Manifest/ManifestDirectoryContents.cs b/src/FileProviders/Embedded/src/Manifest/ManifestDirectoryContents.cs new file mode 100644 index 0000000000..38903dd196 --- /dev/null +++ b/src/FileProviders/Embedded/src/Manifest/ManifestDirectoryContents.cs @@ -0,0 +1,72 @@ +// 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.Collections; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; + +namespace Microsoft.Extensions.FileProviders.Embedded.Manifest +{ + internal class ManifestDirectoryContents : IDirectoryContents + { + private readonly DateTimeOffset _lastModified; + private IFileInfo[] _entries; + + public ManifestDirectoryContents(Assembly assembly, ManifestDirectory directory, DateTimeOffset lastModified) + { + if (assembly == null) + { + throw new ArgumentNullException(nameof(assembly)); + } + + if (directory == null) + { + throw new ArgumentNullException(nameof(directory)); + } + + Assembly = assembly; + Directory = directory; + _lastModified = lastModified; + } + + public bool Exists => true; + + public Assembly Assembly { get; } + + public ManifestDirectory Directory { get; } + + public IEnumerator GetEnumerator() + { + return EnsureEntries().GetEnumerator(); + + IReadOnlyList EnsureEntries() => _entries = _entries ?? ResolveEntries().ToArray(); + + IEnumerable ResolveEntries() + { + if (Directory == ManifestEntry.UnknownPath) + { + yield break; + } + + foreach (var entry in Directory.Children) + { + switch (entry) + { + case ManifestFile f: + yield return new ManifestFileInfo(Assembly, f, _lastModified); + break; + case ManifestDirectory d: + yield return new ManifestDirectoryInfo(d, _lastModified); + break; + default: + throw new InvalidOperationException("Unknown entry type"); + } + } + } + } + + IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); + } +} diff --git a/src/FileProviders/Embedded/src/Manifest/ManifestDirectoryInfo.cs b/src/FileProviders/Embedded/src/Manifest/ManifestDirectoryInfo.cs new file mode 100644 index 0000000000..bfe850445d --- /dev/null +++ b/src/FileProviders/Embedded/src/Manifest/ManifestDirectoryInfo.cs @@ -0,0 +1,39 @@ +// 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.IO; + +namespace Microsoft.Extensions.FileProviders.Embedded.Manifest +{ + internal class ManifestDirectoryInfo : IFileInfo + { + public ManifestDirectoryInfo(ManifestDirectory directory, DateTimeOffset lastModified) + { + if (directory == null) + { + throw new ArgumentNullException(nameof(directory)); + } + + Directory = directory; + LastModified = lastModified; + } + + public bool Exists => true; + + public long Length => -1; + + public string PhysicalPath => null; + + public string Name => Directory.Name; + + public DateTimeOffset LastModified { get; } + + public bool IsDirectory => true; + + public ManifestDirectory Directory { get; } + + public Stream CreateReadStream() => + throw new InvalidOperationException("Cannot create a stream for a directory."); + } +} diff --git a/src/FileProviders/Embedded/src/Manifest/ManifestEntry.cs b/src/FileProviders/Embedded/src/Manifest/ManifestEntry.cs new file mode 100644 index 0000000000..5c8ead2741 --- /dev/null +++ b/src/FileProviders/Embedded/src/Manifest/ManifestEntry.cs @@ -0,0 +1,34 @@ +// 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 Microsoft.Extensions.Primitives; + +namespace Microsoft.Extensions.FileProviders.Embedded.Manifest +{ + internal abstract class ManifestEntry + { + public ManifestEntry(string name) + { + Name = name; + } + + public ManifestEntry Parent { get; private set; } + + public string Name { get; } + + public static ManifestEntry UnknownPath { get; } = ManifestSinkDirectory.Instance; + + protected internal virtual void SetParent(ManifestDirectory directory) + { + if (Parent != null) + { + throw new InvalidOperationException("Directory already has a parent."); + } + + Parent = directory; + } + + public abstract ManifestEntry Traverse(StringSegment segment); + } +} diff --git a/src/FileProviders/Embedded/src/Manifest/ManifestFile.cs b/src/FileProviders/Embedded/src/Manifest/ManifestFile.cs new file mode 100644 index 0000000000..6dd89d3491 --- /dev/null +++ b/src/FileProviders/Embedded/src/Manifest/ManifestFile.cs @@ -0,0 +1,31 @@ +// 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 Microsoft.Extensions.Primitives; + +namespace Microsoft.Extensions.FileProviders.Embedded.Manifest +{ + internal class ManifestFile : ManifestEntry + { + public ManifestFile(string name, string resourcePath) + : base(name) + { + if (string.IsNullOrWhiteSpace(name)) + { + throw new ArgumentException($"'{nameof(name)}' must not be null, empty or whitespace.", nameof(name)); + } + + if (string.IsNullOrWhiteSpace(resourcePath)) + { + throw new ArgumentException($"'{nameof(resourcePath)}' must not be null, empty or whitespace.", nameof(resourcePath)); + } + + ResourcePath = resourcePath; + } + + public string ResourcePath { get; } + + public override ManifestEntry Traverse(StringSegment segment) => UnknownPath; + } +} diff --git a/src/FileProviders/Embedded/src/Manifest/ManifestFileInfo.cs b/src/FileProviders/Embedded/src/Manifest/ManifestFileInfo.cs new file mode 100644 index 0000000000..2329c16f85 --- /dev/null +++ b/src/FileProviders/Embedded/src/Manifest/ManifestFileInfo.cs @@ -0,0 +1,71 @@ +// 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.IO; +using System.Reflection; + +namespace Microsoft.Extensions.FileProviders.Embedded.Manifest +{ + internal class ManifestFileInfo : IFileInfo + { + private long? _length; + + public ManifestFileInfo(Assembly assembly, ManifestFile file, DateTimeOffset lastModified) + { + if (assembly == null) + { + throw new ArgumentNullException(nameof(assembly)); + } + + if (file == null) + { + throw new ArgumentNullException(nameof(file)); + } + + Assembly = assembly; + ManifestFile = file; + LastModified = lastModified; + } + + public Assembly Assembly { get; } + + public ManifestFile ManifestFile { get; } + + public bool Exists => true; + + public long Length => EnsureLength(); + + public string PhysicalPath => null; + + public string Name => ManifestFile.Name; + + public DateTimeOffset LastModified { get; } + + public bool IsDirectory => false; + + private long EnsureLength() + { + if (_length == null) + { + using (var stream = Assembly.GetManifestResourceStream(ManifestFile.ResourcePath)) + { + _length = stream.Length; + } + } + + return _length.Value; + } + + public Stream CreateReadStream() + { + var stream = Assembly.GetManifestResourceStream(ManifestFile.ResourcePath); + if (!_length.HasValue) + { + _length = stream.Length; + } + + return stream; + } + } +} diff --git a/src/FileProviders/Embedded/src/Manifest/ManifestParser.cs b/src/FileProviders/Embedded/src/Manifest/ManifestParser.cs new file mode 100644 index 0000000000..a478b747ca --- /dev/null +++ b/src/FileProviders/Embedded/src/Manifest/ManifestParser.cs @@ -0,0 +1,159 @@ +// 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.Collections.Generic; +using System.Linq; +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Xml; +using System.Xml.Linq; + +namespace Microsoft.Extensions.FileProviders.Embedded.Manifest +{ + internal static class ManifestParser + { + private static readonly string DefaultManifestName = "Microsoft.Extensions.FileProviders.Embedded.Manifest.xml"; + + public static EmbeddedFilesManifest Parse(Assembly assembly) + { + return Parse(assembly, DefaultManifestName); + } + + public static EmbeddedFilesManifest Parse(Assembly assembly, string name) + { + if (assembly == null) + { + throw new ArgumentNullException(nameof(assembly)); + } + + if (name == null) + { + throw new ArgumentNullException(nameof(name)); + } + + var stream = assembly.GetManifestResourceStream(name); + if (stream == null) + { + throw new InvalidOperationException($"Could not load the embedded file manifest " + + $"'{name}' for assembly '{assembly.GetName().Name}'."); + } + + var document = XDocument.Load(stream); + + var manifest = EnsureElement(document, "Manifest"); + var manifestVersion = EnsureElement(manifest, "ManifestVersion"); + var version = EnsureText(manifestVersion); + if (!string.Equals("1.0", version, StringComparison.Ordinal)) + { + throw new InvalidOperationException($"The embedded file manifest '{name}' for " + + $"assembly '{assembly.GetName().Name}' specifies an unsupported file format" + + $" version: '{version}'."); + } + var fileSystem = EnsureElement(manifest, "FileSystem"); + + var entries = fileSystem.Elements(); + var entriesList = new List(); + foreach (var element in entries) + { + var entry = BuildEntry(element); + entriesList.Add(entry); + } + + ValidateEntries(entriesList); + + var rootDirectory = ManifestDirectory.CreateRootDirectory(entriesList.ToArray()); + + return new EmbeddedFilesManifest(rootDirectory); + + } + + private static void ValidateEntries(List entriesList) + { + for (int i = 0; i < entriesList.Count - 1; i++) + { + for (int j = i + 1; j < entriesList.Count; j++) + { + if (string.Equals(entriesList[i].Name, entriesList[j].Name, StringComparison.OrdinalIgnoreCase)) + { + throw new InvalidOperationException( + "Found two entries with the same name but different casing:" + + $" '{entriesList[i].Name}' and '{entriesList[j]}'"); + } + } + } + } + + private static ManifestEntry BuildEntry(XElement element) + { + RuntimeHelpers.EnsureSufficientExecutionStack(); + if (element.NodeType != XmlNodeType.Element) + { + throw new InvalidOperationException($"Invalid manifest format. Expected a 'File' or a 'Directory' node:" + + $" '{element.ToString()}'"); + } + + if (string.Equals(element.Name.LocalName, "File", StringComparison.Ordinal)) + { + var entryName = EnsureName(element); + var path = EnsureElement(element, "ResourcePath"); + var pathValue = EnsureText(path); + return new ManifestFile(entryName, pathValue); + } + + if (string.Equals(element.Name.LocalName, "Directory", StringComparison.Ordinal)) + { + var directoryName = EnsureName(element); + var children = new List(); + foreach (var child in element.Elements()) + { + children.Add(BuildEntry(child)); + } + + ValidateEntries(children); + + return ManifestDirectory.CreateDirectory(directoryName, children.ToArray()); + } + + throw new InvalidOperationException($"Invalid manifest format.Expected a 'File' or a 'Directory' node. " + + $"Got '{element.Name.LocalName}' instead."); + } + + private static XElement EnsureElement(XContainer container, string elementName) + { + var element = container.Element(elementName); + if (element == null) + { + throw new InvalidOperationException($"Invalid manifest format. Missing '{elementName}' element name"); + } + + return element; + } + + private static string EnsureName(XElement element) + { + var value = element.Attribute("Name")?.Value; + if (value == null) + { + throw new InvalidOperationException($"Invalid manifest format. '{element.Name}' must contain a 'Name' attribute."); + } + + return value; + } + + private static string EnsureText(XElement element) + { + if (element.Elements().Count() == 0 && + !element.IsEmpty && + element.Nodes().Count() == 1 && + element.FirstNode.NodeType == XmlNodeType.Text) + { + return element.Value; + } + + throw new InvalidOperationException( + $"Invalid manifest format. '{element.Name.LocalName}' must contain " + + $"a text value. '{element.Value}'"); + } + } +} diff --git a/src/FileProviders/Embedded/src/Manifest/ManifestRootDirectory.cs b/src/FileProviders/Embedded/src/Manifest/ManifestRootDirectory.cs new file mode 100644 index 0000000000..1e5999e906 --- /dev/null +++ b/src/FileProviders/Embedded/src/Manifest/ManifestRootDirectory.cs @@ -0,0 +1,16 @@ +// 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. + +namespace Microsoft.Extensions.FileProviders.Embedded.Manifest +{ + internal class ManifestRootDirectory : ManifestDirectory + { + public ManifestRootDirectory(ManifestEntry[] children) + : base(name: null, children: children) + { + SetParent(ManifestSinkDirectory.Instance); + } + + public override ManifestDirectory ToRootDirectory() => this; + } +} diff --git a/src/FileProviders/Embedded/src/Manifest/ManifestSinkDirectory.cs b/src/FileProviders/Embedded/src/Manifest/ManifestSinkDirectory.cs new file mode 100644 index 0000000000..f14908534f --- /dev/null +++ b/src/FileProviders/Embedded/src/Manifest/ManifestSinkDirectory.cs @@ -0,0 +1,22 @@ +// 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 Microsoft.Extensions.Primitives; + +namespace Microsoft.Extensions.FileProviders.Embedded.Manifest +{ + internal class ManifestSinkDirectory : ManifestDirectory + { + private ManifestSinkDirectory() + : base(name: null, children: Array.Empty()) + { + SetParent(this); + Children = new[] { this }; + } + + public static ManifestDirectory Instance { get; } = new ManifestSinkDirectory(); + + public override ManifestEntry Traverse(StringSegment segment) => this; + } +} diff --git a/src/FileProviders/Embedded/src/ManifestEmbeddedFileProvider.cs b/src/FileProviders/Embedded/src/ManifestEmbeddedFileProvider.cs new file mode 100644 index 0000000000..f639a2a812 --- /dev/null +++ b/src/FileProviders/Embedded/src/ManifestEmbeddedFileProvider.cs @@ -0,0 +1,153 @@ +// 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.IO; +using System.Reflection; +using Microsoft.Extensions.FileProviders.Embedded.Manifest; +using Microsoft.Extensions.Primitives; + +namespace Microsoft.Extensions.FileProviders +{ + /// + /// An embedded file provider that uses a manifest compiled in the assembly to + /// reconstruct the original paths of the embedded files when they were embedded + /// into the assembly. + /// + public class ManifestEmbeddedFileProvider : IFileProvider + { + private readonly DateTimeOffset _lastModified; + + /// + /// Initializes a new instance of . + /// + /// The assembly containing the embedded files. + public ManifestEmbeddedFileProvider(Assembly assembly) + : this(assembly, ManifestParser.Parse(assembly), ResolveLastModified(assembly)) { } + + /// + /// Initializes a new instance of . + /// + /// The assembly containing the embedded files. + /// The relative path from the root of the manifest to use as root for the provider. + public ManifestEmbeddedFileProvider(Assembly assembly, string root) + : this(assembly, root, ResolveLastModified(assembly)) + { + } + + /// + /// Initializes a new instance of . + /// + /// The assembly containing the embedded files. + /// The relative path from the root of the manifest to use as root for the provider. + /// The LastModified date to use on the instances + /// returned by this . + public ManifestEmbeddedFileProvider(Assembly assembly, string root, DateTimeOffset lastModified) + : this(assembly, ManifestParser.Parse(assembly).Scope(root), lastModified) + { + } + + /// + /// Initializes a new instance of . + /// + /// The assembly containing the embedded files. + /// The relative path from the root of the manifest to use as root for the provider. + /// The name of the embedded resource containing the manifest. + /// The LastModified date to use on the instances + /// returned by this . + public ManifestEmbeddedFileProvider(Assembly assembly, string root, string manifestName, DateTimeOffset lastModified) + : this(assembly, ManifestParser.Parse(assembly, manifestName).Scope(root), lastModified) + { + } + + internal ManifestEmbeddedFileProvider(Assembly assembly, EmbeddedFilesManifest manifest, DateTimeOffset lastModified) + { + if (assembly == null) + { + throw new ArgumentNullException(nameof(assembly)); + } + + if (manifest == null) + { + throw new ArgumentNullException(nameof(manifest)); + } + + Assembly = assembly; + Manifest = manifest; + _lastModified = lastModified; + } + + /// + /// Gets the for this provider. + /// + public Assembly Assembly { get; } + + internal EmbeddedFilesManifest Manifest { get; } + + /// + public IDirectoryContents GetDirectoryContents(string subpath) + { + var entry = Manifest.ResolveEntry(subpath); + if (entry == null || entry == ManifestEntry.UnknownPath) + { + return NotFoundDirectoryContents.Singleton; + } + + if (!(entry is ManifestDirectory directory)) + { + return NotFoundDirectoryContents.Singleton; + } + + return new ManifestDirectoryContents(Assembly, directory, _lastModified); + } + + /// + public IFileInfo GetFileInfo(string subpath) + { + var entry = Manifest.ResolveEntry(subpath); + switch (entry) + { + case null: + return new NotFoundFileInfo(subpath); + case ManifestFile f: + return new ManifestFileInfo(Assembly, f, _lastModified); + case ManifestDirectory d when d != ManifestEntry.UnknownPath: + return new NotFoundFileInfo(d.Name); + } + + return new NotFoundFileInfo(subpath); + } + + /// + public IChangeToken Watch(string filter) + { + if (filter == null) + { + throw new ArgumentNullException(nameof(filter)); + } + + return NullChangeToken.Singleton; + } + + private static DateTimeOffset ResolveLastModified(Assembly assembly) + { + var result = DateTimeOffset.UtcNow; + + if (!string.IsNullOrEmpty(assembly.Location)) + { + try + { + result = File.GetLastWriteTimeUtc(assembly.Location); + } + catch (PathTooLongException) + { + } + catch (UnauthorizedAccessException) + { + } + } + + return result; + } + } +} diff --git a/src/FileProviders/Embedded/src/Microsoft.Extensions.FileProviders.Embedded.csproj b/src/FileProviders/Embedded/src/Microsoft.Extensions.FileProviders.Embedded.csproj new file mode 100644 index 0000000000..d7ca20b469 --- /dev/null +++ b/src/FileProviders/Embedded/src/Microsoft.Extensions.FileProviders.Embedded.csproj @@ -0,0 +1,46 @@ + + + + Microsoft.Extensions.FileProviders + File provider for files in embedded resources for Microsoft.Extensions.FileProviders. + netstandard2.0 + $(MSBuildProjectName).nuspec + + + + + + + + + + + + id=$(PackageId); + version=$(PackageVersion); + authors=$(Authors); + description=$(Description); + tags=$(PackageTags.Replace(';', ' ')); + licenseUrl=$(PackageLicenseUrl); + projectUrl=$(PackageProjectUrl); + iconUrl=$(PackageIconUrl); + repositoryUrl=$(RepositoryUrl); + repositoryCommit=$(RepositoryCommit); + copyright=$(Copyright); + targetframework=$(TargetFramework); + AssemblyName=$(AssemblyName); + + OutputBinary=@(BuiltProjectOutputGroupOutput); + OutputSymbol=@(DebugSymbolsProjectOutputGroupOutput); + OutputDocumentation=@(DocumentationProjectOutputGroupOutput); + + + TaskAssemblyNetStandard=..\..\Manifest.MSBuildTask\src\bin\$(Configuration)\netstandard1.5\$(AssemblyName).Manifest.Task.dll; + TaskSymbolNetStandard=..\..\Manifest.MSBuildTask\src\bin\$(Configuration)\netstandard1.5\$(AssemblyName).Manifest.Task.pdb; + TaskAssemblyNet461=..\..\Manifest.MSBuildTask\src\bin\$(Configuration)\net461\$(AssemblyName).Manifest.Task.dll; + TaskSymbolNet461=..\..\Manifest.MSBuildTask\src\bin\$(Configuration)\net461\$(AssemblyName).Manifest.Task.pdb; + + + + + diff --git a/src/FileProviders/Embedded/src/Microsoft.Extensions.FileProviders.Embedded.nuspec b/src/FileProviders/Embedded/src/Microsoft.Extensions.FileProviders.Embedded.nuspec new file mode 100644 index 0000000000..0cc5ed823a --- /dev/null +++ b/src/FileProviders/Embedded/src/Microsoft.Extensions.FileProviders.Embedded.nuspec @@ -0,0 +1,33 @@ + + + + $id$ + $version$ + $authors$ + true + $licenseUrl$ + $projectUrl$ + $iconUrl$ + $description$ + $copyright$ + $tags$ + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/FileProviders/Embedded/src/Properties/AssemblyInfo.cs b/src/FileProviders/Embedded/src/Properties/AssemblyInfo.cs new file mode 100644 index 0000000000..610a7fa706 --- /dev/null +++ b/src/FileProviders/Embedded/src/Properties/AssemblyInfo.cs @@ -0,0 +1,4 @@ + +using System.Runtime.CompilerServices; + +[assembly: InternalsVisibleTo("Microsoft.Extensions.FileProviders.Embedded.Tests, PublicKey=0024000004800000940000000602000000240000525341310004000001000100f33a29044fa9d740c9b3213a93e57c84b472c84e0b8a0e1ae48e67a9f8f6de9d5f7f3d52ac23e48ac51801f1dc950abe901da34d2a9e3baadb141a17c77ef3c565dd5ee5054b91cf63bb3c6ab83f72ab3aafe93d0fc3c2348b764fafb0b1c0733de51459aeab46580384bf9d74c4e28164b7cde247f891ba07891c9d872ad2bb")] \ No newline at end of file diff --git a/src/FileProviders/Embedded/src/baseline.netcore.json b/src/FileProviders/Embedded/src/baseline.netcore.json new file mode 100644 index 0000000000..821969ea0b --- /dev/null +++ b/src/FileProviders/Embedded/src/baseline.netcore.json @@ -0,0 +1,343 @@ +{ + "AssemblyIdentity": "Microsoft.Extensions.FileProviders.Embedded, Version=2.1.1.0, Culture=neutral, PublicKeyToken=adb9793829ddae60", + "Types": [ + { + "Name": "Microsoft.Extensions.FileProviders.EmbeddedFileProvider", + "Visibility": "Public", + "Kind": "Class", + "ImplementedInterfaces": [ + "Microsoft.Extensions.FileProviders.IFileProvider" + ], + "Members": [ + { + "Kind": "Method", + "Name": "GetFileInfo", + "Parameters": [ + { + "Name": "subpath", + "Type": "System.String" + } + ], + "ReturnType": "Microsoft.Extensions.FileProviders.IFileInfo", + "Sealed": true, + "Virtual": true, + "ImplementedInterface": "Microsoft.Extensions.FileProviders.IFileProvider", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "GetDirectoryContents", + "Parameters": [ + { + "Name": "subpath", + "Type": "System.String" + } + ], + "ReturnType": "Microsoft.Extensions.FileProviders.IDirectoryContents", + "Sealed": true, + "Virtual": true, + "ImplementedInterface": "Microsoft.Extensions.FileProviders.IFileProvider", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "Watch", + "Parameters": [ + { + "Name": "pattern", + "Type": "System.String" + } + ], + "ReturnType": "Microsoft.Extensions.Primitives.IChangeToken", + "Sealed": true, + "Virtual": true, + "ImplementedInterface": "Microsoft.Extensions.FileProviders.IFileProvider", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Constructor", + "Name": ".ctor", + "Parameters": [ + { + "Name": "assembly", + "Type": "System.Reflection.Assembly" + } + ], + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Constructor", + "Name": ".ctor", + "Parameters": [ + { + "Name": "assembly", + "Type": "System.Reflection.Assembly" + }, + { + "Name": "baseNamespace", + "Type": "System.String" + } + ], + "Visibility": "Public", + "GenericParameter": [] + } + ], + "GenericParameters": [] + }, + { + "Name": "Microsoft.Extensions.FileProviders.ManifestEmbeddedFileProvider", + "Visibility": "Public", + "Kind": "Class", + "ImplementedInterfaces": [ + "Microsoft.Extensions.FileProviders.IFileProvider" + ], + "Members": [ + { + "Kind": "Method", + "Name": "get_Assembly", + "Parameters": [], + "ReturnType": "System.Reflection.Assembly", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "GetDirectoryContents", + "Parameters": [ + { + "Name": "subpath", + "Type": "System.String" + } + ], + "ReturnType": "Microsoft.Extensions.FileProviders.IDirectoryContents", + "Sealed": true, + "Virtual": true, + "ImplementedInterface": "Microsoft.Extensions.FileProviders.IFileProvider", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "GetFileInfo", + "Parameters": [ + { + "Name": "subpath", + "Type": "System.String" + } + ], + "ReturnType": "Microsoft.Extensions.FileProviders.IFileInfo", + "Sealed": true, + "Virtual": true, + "ImplementedInterface": "Microsoft.Extensions.FileProviders.IFileProvider", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "Watch", + "Parameters": [ + { + "Name": "filter", + "Type": "System.String" + } + ], + "ReturnType": "Microsoft.Extensions.Primitives.IChangeToken", + "Sealed": true, + "Virtual": true, + "ImplementedInterface": "Microsoft.Extensions.FileProviders.IFileProvider", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Constructor", + "Name": ".ctor", + "Parameters": [ + { + "Name": "assembly", + "Type": "System.Reflection.Assembly" + } + ], + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Constructor", + "Name": ".ctor", + "Parameters": [ + { + "Name": "assembly", + "Type": "System.Reflection.Assembly" + }, + { + "Name": "root", + "Type": "System.String" + } + ], + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Constructor", + "Name": ".ctor", + "Parameters": [ + { + "Name": "assembly", + "Type": "System.Reflection.Assembly" + }, + { + "Name": "root", + "Type": "System.String" + }, + { + "Name": "lastModified", + "Type": "System.DateTimeOffset" + } + ], + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Constructor", + "Name": ".ctor", + "Parameters": [ + { + "Name": "assembly", + "Type": "System.Reflection.Assembly" + }, + { + "Name": "root", + "Type": "System.String" + }, + { + "Name": "manifestName", + "Type": "System.String" + }, + { + "Name": "lastModified", + "Type": "System.DateTimeOffset" + } + ], + "Visibility": "Public", + "GenericParameter": [] + } + ], + "GenericParameters": [] + }, + { + "Name": "Microsoft.Extensions.FileProviders.Embedded.EmbeddedResourceFileInfo", + "Visibility": "Public", + "Kind": "Class", + "ImplementedInterfaces": [ + "Microsoft.Extensions.FileProviders.IFileInfo" + ], + "Members": [ + { + "Kind": "Method", + "Name": "get_Exists", + "Parameters": [], + "ReturnType": "System.Boolean", + "Sealed": true, + "Virtual": true, + "ImplementedInterface": "Microsoft.Extensions.FileProviders.IFileInfo", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "get_Length", + "Parameters": [], + "ReturnType": "System.Int64", + "Sealed": true, + "Virtual": true, + "ImplementedInterface": "Microsoft.Extensions.FileProviders.IFileInfo", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "get_PhysicalPath", + "Parameters": [], + "ReturnType": "System.String", + "Sealed": true, + "Virtual": true, + "ImplementedInterface": "Microsoft.Extensions.FileProviders.IFileInfo", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "get_Name", + "Parameters": [], + "ReturnType": "System.String", + "Sealed": true, + "Virtual": true, + "ImplementedInterface": "Microsoft.Extensions.FileProviders.IFileInfo", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "get_LastModified", + "Parameters": [], + "ReturnType": "System.DateTimeOffset", + "Sealed": true, + "Virtual": true, + "ImplementedInterface": "Microsoft.Extensions.FileProviders.IFileInfo", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "get_IsDirectory", + "Parameters": [], + "ReturnType": "System.Boolean", + "Sealed": true, + "Virtual": true, + "ImplementedInterface": "Microsoft.Extensions.FileProviders.IFileInfo", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "CreateReadStream", + "Parameters": [], + "ReturnType": "System.IO.Stream", + "Sealed": true, + "Virtual": true, + "ImplementedInterface": "Microsoft.Extensions.FileProviders.IFileInfo", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Constructor", + "Name": ".ctor", + "Parameters": [ + { + "Name": "assembly", + "Type": "System.Reflection.Assembly" + }, + { + "Name": "resourcePath", + "Type": "System.String" + }, + { + "Name": "name", + "Type": "System.String" + }, + { + "Name": "lastModified", + "Type": "System.DateTimeOffset" + } + ], + "Visibility": "Public", + "GenericParameter": [] + } + ], + "GenericParameters": [] + } + ] +} \ No newline at end of file diff --git a/src/FileProviders/Embedded/src/build/netstandard2.0/Microsoft.Extensions.FileProviders.Embedded.props b/src/FileProviders/Embedded/src/build/netstandard2.0/Microsoft.Extensions.FileProviders.Embedded.props new file mode 100644 index 0000000000..e913e17321 --- /dev/null +++ b/src/FileProviders/Embedded/src/build/netstandard2.0/Microsoft.Extensions.FileProviders.Embedded.props @@ -0,0 +1,17 @@ + + + false + Microsoft.Extensions.FileProviders.Embedded.Manifest.xml + + + + <_FileProviderTaskFolder Condition="'$(MSBuildRuntimeType)' == 'Core'">netstandard1.5 + <_FileProviderTaskFolder Condition="'$(MSBuildRuntimeType)' != 'Core'">net461 + <_FileProviderTaskAssembly>$(MSBuildThisFileDirectory)..\..\tasks\$(_FileProviderTaskFolder)\Microsoft.Extensions.FileProviders.Embedded.Manifest.Task.dll + + + + + diff --git a/src/FileProviders/Embedded/src/build/netstandard2.0/Microsoft.Extensions.FileProviders.Embedded.targets b/src/FileProviders/Embedded/src/build/netstandard2.0/Microsoft.Extensions.FileProviders.Embedded.targets new file mode 100644 index 0000000000..83505d7fea --- /dev/null +++ b/src/FileProviders/Embedded/src/build/netstandard2.0/Microsoft.Extensions.FileProviders.Embedded.targets @@ -0,0 +1,69 @@ + + + _CalculateEmbeddedFilesManifestInputs;$(PrepareResourceNamesDependsOn) + + + + + + <_GeneratedManifestFile>$(IntermediateOutputPath)$(EmbeddedFilesManifestFileName) + + + + <_FilesForManifest Include="@(EmbeddedResource)" /> + <_FilesForManifest Remove="@(EmbeddedResource->WithMetadataValue('ExcludeFromManifest','true'))" /> + + + + + + + + + + + + <_GeneratedManifestInfoInputsCacheFile>$(IntermediateOutputPath)$(MSBuildProjectName).EmbeddedFilesManifest.cache + + + + + + + + + + + + + + + + + + <_FilesForManifest Remove="@(_FilesForManifest)" /> + <_FilesForManifest Include="@(EmbeddedResource)" /> + <_FilesForManifest Remove="@(EmbeddedResource->WithMetadataValue('ExcludeFromManifest','true'))" /> + + + + + diff --git a/src/FileProviders/Embedded/src/buildMultiTargeting/Microsoft.Extensions.FileProviders.Embedded.props b/src/FileProviders/Embedded/src/buildMultiTargeting/Microsoft.Extensions.FileProviders.Embedded.props new file mode 100644 index 0000000000..87296f28f3 --- /dev/null +++ b/src/FileProviders/Embedded/src/buildMultiTargeting/Microsoft.Extensions.FileProviders.Embedded.props @@ -0,0 +1,3 @@ + + + diff --git a/src/FileProviders/Embedded/src/buildMultiTargeting/Microsoft.Extensions.FileProviders.Embedded.targets b/src/FileProviders/Embedded/src/buildMultiTargeting/Microsoft.Extensions.FileProviders.Embedded.targets new file mode 100644 index 0000000000..9191097036 --- /dev/null +++ b/src/FileProviders/Embedded/src/buildMultiTargeting/Microsoft.Extensions.FileProviders.Embedded.targets @@ -0,0 +1,3 @@ + + + diff --git a/src/FileProviders/Embedded/test/EmbeddedFileProviderTests.cs b/src/FileProviders/Embedded/test/EmbeddedFileProviderTests.cs new file mode 100644 index 0000000000..cb9598a1b4 --- /dev/null +++ b/src/FileProviders/Embedded/test/EmbeddedFileProviderTests.cs @@ -0,0 +1,231 @@ +// 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.Linq; +using System.Reflection; +using Microsoft.AspNetCore.Testing; +using Xunit; + +namespace Microsoft.Extensions.FileProviders.Embedded.Tests +{ + public class EmbeddedFileProviderTests + { + private static readonly string Namespace = typeof(EmbeddedFileProviderTests).Namespace; + + [Fact] + public void ConstructorWithNullAssemblyThrowsArgumentException() + { + Assert.Throws(() => new EmbeddedFileProvider(null)); + } + + [Fact] + public void GetFileInfo_ReturnsNotFoundFileInfo_IfFileDoesNotExist() + { + // Arrange + var provider = new EmbeddedFileProvider(GetType().GetTypeInfo().Assembly); + + // Act + var fileInfo = provider.GetFileInfo("DoesNotExist.Txt"); + + // Assert + Assert.NotNull(fileInfo); + Assert.False(fileInfo.Exists); + } + + [Theory] + [InlineData("File.txt")] + [InlineData("/File.txt")] + public void GetFileInfo_ReturnsFilesAtRoot(string filePath) + { + // Arrange + var provider = new EmbeddedFileProvider(GetType().GetTypeInfo().Assembly); + var expectedFileLength = 8; + + // Act + var fileInfo = provider.GetFileInfo(filePath); + + // Assert + Assert.NotNull(fileInfo); + Assert.True(fileInfo.Exists); + Assert.NotEqual(default(DateTimeOffset), fileInfo.LastModified); + Assert.Equal(expectedFileLength, fileInfo.Length); + Assert.False(fileInfo.IsDirectory); + Assert.Null(fileInfo.PhysicalPath); + Assert.Equal("File.txt", fileInfo.Name); + } + + [Fact] + public void GetFileInfo_ReturnsNotFoundFileInfo_IfFileDoesNotExistUnderSpecifiedNamespace() + { + // Arrange + var provider = new EmbeddedFileProvider(GetType().GetTypeInfo().Assembly, Namespace + ".SubNamespace"); + + // Act + var fileInfo = provider.GetFileInfo("File.txt"); + + // Assert + Assert.NotNull(fileInfo); + Assert.False(fileInfo.Exists); + } + + [Fact] + public void GetFileInfo_ReturnsNotFoundIfPathStartsWithBackSlash() + { + // Arrange + var provider = new EmbeddedFileProvider(GetType().GetTypeInfo().Assembly); + + // Act + var fileInfo = provider.GetFileInfo("\\File.txt"); + + // Assert + Assert.NotNull(fileInfo); + Assert.False(fileInfo.Exists); + } + + public static TheoryData GetFileInfo_LocatesFilesUnderSpecifiedNamespaceData + { + get + { + var theoryData = new TheoryData + { + "ResourcesInSubdirectory/File3.txt" + }; + + if (TestPlatformHelper.IsWindows) + { + theoryData.Add("ResourcesInSubdirectory\\File3.txt"); + } + + return theoryData; + } + } + + [Theory] + [MemberData(nameof(GetFileInfo_LocatesFilesUnderSpecifiedNamespaceData))] + public void GetFileInfo_LocatesFilesUnderSpecifiedNamespace(string path) + { + // Arrange + var provider = new EmbeddedFileProvider(GetType().GetTypeInfo().Assembly, Namespace + ".Resources"); + + // Act + var fileInfo = provider.GetFileInfo(path); + + // Assert + Assert.NotNull(fileInfo); + Assert.True(fileInfo.Exists); + Assert.NotEqual(default(DateTimeOffset), fileInfo.LastModified); + Assert.True(fileInfo.Length > 0); + Assert.False(fileInfo.IsDirectory); + Assert.Null(fileInfo.PhysicalPath); + Assert.Equal("File3.txt", fileInfo.Name); + } + + public static TheoryData GetFileInfo_LocatesFilesUnderSubDirectoriesData + { + get + { + var theoryData = new TheoryData + { + "Resources/File.txt" + }; + + if (TestPlatformHelper.IsWindows) + { + theoryData.Add("Resources\\File.txt"); + } + + return theoryData; + } + } + + [Theory] + [MemberData(nameof(GetFileInfo_LocatesFilesUnderSubDirectoriesData))] + public void GetFileInfo_LocatesFilesUnderSubDirectories(string path) + { + // Arrange + var provider = new EmbeddedFileProvider(GetType().GetTypeInfo().Assembly); + + // Act + var fileInfo = provider.GetFileInfo(path); + + // Assert + Assert.NotNull(fileInfo); + Assert.True(fileInfo.Exists); + Assert.NotEqual(default(DateTimeOffset), fileInfo.LastModified); + Assert.True(fileInfo.Length > 0); + Assert.False(fileInfo.IsDirectory); + Assert.Null(fileInfo.PhysicalPath); + Assert.Equal("File.txt", fileInfo.Name); + } + + [Theory] + [InlineData("")] + [InlineData("/")] + public void GetDirectoryContents_ReturnsAllFilesInFileSystem(string path) + { + // Arrange + var provider = new EmbeddedFileProvider(GetType().GetTypeInfo().Assembly, Namespace + ".Resources"); + + // Act + var files = provider.GetDirectoryContents(path); + + // Assert + Assert.Collection(files.OrderBy(f => f.Name, StringComparer.Ordinal), + file => Assert.Equal("File.txt", file.Name), + file => Assert.Equal("ResourcesInSubdirectory.File3.txt", file.Name)); + + Assert.False(provider.GetDirectoryContents("file").Exists); + Assert.False(provider.GetDirectoryContents("file/").Exists); + Assert.False(provider.GetDirectoryContents("file.txt").Exists); + Assert.False(provider.GetDirectoryContents("file/txt").Exists); + } + + [Fact] + public void GetDirectoryContents_ReturnsEmptySequence_IfResourcesDoNotExistUnderNamespace() + { + // Arrange + var provider = new EmbeddedFileProvider(GetType().GetTypeInfo().Assembly, "Unknown.Namespace"); + + // Act + var files = provider.GetDirectoryContents(string.Empty); + + // Assert + Assert.NotNull(files); + Assert.True(files.Exists); + Assert.Empty(files); + } + + [Theory] + [InlineData("Resources")] + [InlineData("/Resources")] + public void GetDirectoryContents_ReturnsNotFoundDirectoryContents_IfHierarchicalPathIsSpecified(string path) + { + // Arrange + var provider = new EmbeddedFileProvider(GetType().GetTypeInfo().Assembly); + + // Act + var files = provider.GetDirectoryContents(path); + + // Assert + Assert.NotNull(files); + Assert.False(files.Exists); + Assert.Empty(files); + } + + [Fact] + public void Watch_ReturnsNoOpTrigger() + { + // Arange + var provider = new EmbeddedFileProvider(GetType().GetTypeInfo().Assembly); + + // Act + var token = provider.Watch("Resources/File.txt"); + + // Assert + Assert.NotNull(token); + Assert.False(token.ActiveChangeCallbacks); + Assert.False(token.HasChanged); + } + } +} \ No newline at end of file diff --git a/src/FileProviders/Embedded/test/File.txt b/src/FileProviders/Embedded/test/File.txt new file mode 100644 index 0000000000..357323fbfa --- /dev/null +++ b/src/FileProviders/Embedded/test/File.txt @@ -0,0 +1 @@ +Hello \ No newline at end of file diff --git a/src/FileProviders/Embedded/test/FileInfoComparer.cs b/src/FileProviders/Embedded/test/FileInfoComparer.cs new file mode 100644 index 0000000000..1b4b69b4c1 --- /dev/null +++ b/src/FileProviders/Embedded/test/FileInfoComparer.cs @@ -0,0 +1,34 @@ +// 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.Collections.Generic; + +namespace Microsoft.Extensions.FileProviders +{ + internal class FileInfoComparer : IEqualityComparer + { + public static FileInfoComparer Instance { get; set; } = new FileInfoComparer(); + + public bool Equals(IFileInfo x, IFileInfo y) + { + if (x == null && y == null) + { + return true; + } + + if ((x == null && y != null) || (x != null && y == null)) + { + return false; + } + + return x.Exists == y.Exists && + x.IsDirectory == y.IsDirectory && + x.Length == y.Length && + string.Equals(x.Name, y.Name, StringComparison.Ordinal) && + string.Equals(x.PhysicalPath, y.PhysicalPath, StringComparison.Ordinal); + } + + public int GetHashCode(IFileInfo obj) => 0; + } +} diff --git a/src/FileProviders/Embedded/test/Manifest/EmbeddedFilesManifestTests.cs b/src/FileProviders/Embedded/test/Manifest/EmbeddedFilesManifestTests.cs new file mode 100644 index 0000000000..107491d34a --- /dev/null +++ b/src/FileProviders/Embedded/test/Manifest/EmbeddedFilesManifestTests.cs @@ -0,0 +1,58 @@ +// 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 Xunit; + +namespace Microsoft.Extensions.FileProviders.Embedded.Manifest +{ + public class EmbeddedFilesManifestTests + { + [Theory] + [InlineData("/wwwroot//jquery.validate.js")] + [InlineData("//wwwroot/jquery.validate.js")] + public void ResolveEntry_IgnoresInvalidPaths(string path) + { + // Arrange + var manifest = new EmbeddedFilesManifest( + ManifestDirectory.CreateRootDirectory( + new[] + { + ManifestDirectory.CreateDirectory("wwwroot", + new[] + { + new ManifestFile("jquery.validate.js","wwwroot.jquery.validate.js") + }) + })); + // Act + var entry = manifest.ResolveEntry(path); + + // Assert + Assert.Null(entry); + } + + [Theory] + [InlineData("/")] + [InlineData("./")] + [InlineData("/wwwroot/jquery.validate.js")] + [InlineData("/wwwroot/")] + public void ResolveEntry_AllowsSingleDirectorySeparator(string path) + { + // Arrange + var manifest = new EmbeddedFilesManifest( + ManifestDirectory.CreateRootDirectory( + new[] + { + ManifestDirectory.CreateDirectory("wwwroot", + new[] + { + new ManifestFile("jquery.validate.js","wwwroot.jquery.validate.js") + }) + })); + // Act + var entry = manifest.ResolveEntry(path); + + // Assert + Assert.NotNull(entry); + } + } +} diff --git a/src/FileProviders/Embedded/test/Manifest/ManifestEntryTests.cs b/src/FileProviders/Embedded/test/Manifest/ManifestEntryTests.cs new file mode 100644 index 0000000000..dc1a7e1cdd --- /dev/null +++ b/src/FileProviders/Embedded/test/Manifest/ManifestEntryTests.cs @@ -0,0 +1,113 @@ +// 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 Xunit; + +namespace Microsoft.Extensions.FileProviders.Embedded.Manifest +{ + public class ManifestEntryTests + { + [Fact] + public void TraversingAFile_ReturnsUnknownPath() + { + // Arrange + var file = new ManifestFile("a", "a.b.c"); + + // Act + var result = file.Traverse("."); + + // Assert + Assert.Equal(ManifestEntry.UnknownPath, result); + } + + [Fact] + public void TraversingANonExistingFile_ReturnsUnknownPath() + { + // Arrange + var directory = ManifestDirectory.CreateDirectory("a", Array.Empty()); + + // Act + var result = directory.Traverse("missing.txt"); + + // Assert + Assert.Equal(ManifestEntry.UnknownPath, result); + } + + [Fact] + public void TraversingWithDot_ReturnsSelf() + { + // Arrange + var directory = ManifestDirectory.CreateDirectory("a", Array.Empty()); + + // Act + var result = directory.Traverse("."); + + // Assert + Assert.Same(directory, result); + } + + [Fact] + public void TraversingWithDotDot_ReturnsParent() + { + // Arrange + var childDirectory = ManifestDirectory.CreateDirectory("b", Array.Empty()); + var directory = ManifestDirectory.CreateDirectory("a", new[] { childDirectory }); + + // Act + var result = childDirectory.Traverse(".."); + + // Assert + Assert.Equal(directory, result); + } + + [Fact] + public void TraversingRootDirectoryWithDotDot_ReturnsSinkDirectory() + { + // Arrange + var directory = ManifestDirectory.CreateRootDirectory(Array.Empty()); + + // Act + var result = directory.Traverse(".."); + + // Assert + Assert.Equal(ManifestEntry.UnknownPath, result); + } + + [Fact] + public void ScopingAFolderAndTryingToGetAScopedFile_ReturnsSinkDirectory() + { + // Arrange + var directory = ManifestDirectory.CreateRootDirectory(new[] { + ManifestDirectory.CreateDirectory("a", + new[] { new ManifestFile("test1.txt", "text.txt") }), + ManifestDirectory.CreateDirectory("b", + new[] { new ManifestFile("test2.txt", "test2.txt") }) }); + + var newRoot = ((ManifestDirectory)directory.Traverse("a")).ToRootDirectory(); + + // Act + var result = newRoot.Traverse("../b/test.txt"); + + // Assert + Assert.Same(ManifestEntry.UnknownPath, result); + } + + [Theory] + [InlineData("..")] + [InlineData(".")] + [InlineData("file.txt")] + [InlineData("folder")] + public void TraversingUnknownPath_ReturnsSinkDirectory(string path) + { + // Arrange + var directory = ManifestEntry.UnknownPath; + + // Act + var result = directory.Traverse(path); + + // Assert + Assert.Equal(ManifestEntry.UnknownPath, result); + } + } +} diff --git a/src/FileProviders/Embedded/test/Manifest/ManifestParserTests.cs b/src/FileProviders/Embedded/test/Manifest/ManifestParserTests.cs new file mode 100644 index 0000000000..e4edc8bde0 --- /dev/null +++ b/src/FileProviders/Embedded/test/Manifest/ManifestParserTests.cs @@ -0,0 +1,116 @@ +// 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 Xunit; + +namespace Microsoft.Extensions.FileProviders.Embedded.Manifest +{ + public class ManifestParserTests + { + [Fact] + public void Parse_UsesDefaultManifestNameForManifest() + { + // Arrange + var assembly = new TestAssembly( + TestEntry.Directory("unused", + TestEntry.File("sample.txt"))); + + // Act + var manifest = ManifestParser.Parse(assembly); + + // Assert + Assert.NotNull(manifest); + } + + [Fact] + public void Parse_FindsManifestWithCustomName() + { + // Arrange + var assembly = new TestAssembly( + TestEntry.Directory("unused", + TestEntry.File("sample.txt")), + manifestName: "Manifest.xml"); + + // Act + var manifest = ManifestParser.Parse(assembly, "Manifest.xml"); + + // Assert + Assert.NotNull(manifest); + } + + [Fact] + public void Parse_ThrowsForEntriesWithDifferentCasing() + { + // Arrange + var assembly = new TestAssembly( + TestEntry.Directory("unused", + TestEntry.File("sample.txt"), + TestEntry.File("SAMPLE.TXT"))); + + // Act & Assert + Assert.Throws(() => ManifestParser.Parse(assembly)); + } + + [Theory] + [MemberData(nameof(MalformedManifests))] + public void Parse_ThrowsForInvalidManifests(string invalidManifest) + { + // Arrange + var assembly = new TestAssembly(invalidManifest); + + // Act & Assert + Assert.Throws(() => ManifestParser.Parse(assembly)); + } + + public static TheoryData MalformedManifests => + new TheoryData + { + "", + "", + "", + "2.0", + "2.0", + @"1.0 +path", + + @"1.0 +", + + @"1.0 +sample.txt", + + @"1.0 +", + + @"1.0 +" + }; + + [Theory] + [MemberData(nameof(ManifestsWithAdditionalData))] + public void Parse_IgnoresAdditionalDataOnFileAndDirectoryNodes(string manifest) + { + // Arrange + var assembly = new TestAssembly(manifest); + + // Act + var result = ManifestParser.Parse(assembly); + + // Assert + Assert.NotNull(result); + } + + public static TheoryData ManifestsWithAdditionalData => + new TheoryData + { + @"1.0 +", + + @"1.0 + +path1234 +" + }; + } +} diff --git a/src/FileProviders/Embedded/test/Manifest/TestEntry.cs b/src/FileProviders/Embedded/test/Manifest/TestEntry.cs new file mode 100644 index 0000000000..aaaf881469 --- /dev/null +++ b/src/FileProviders/Embedded/test/Manifest/TestEntry.cs @@ -0,0 +1,41 @@ +// 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.Collections.Generic; +using System.Linq; +using System.Xml.Linq; + +namespace Microsoft.Extensions.FileProviders.Embedded.Manifest +{ + class TestEntry + { + public bool IsFile => ResourcePath != null; + public string Name { get; set; } + public TestEntry[] Children { get; set; } + public string ResourcePath { get; set; } + + public static TestEntry Directory(string name, params TestEntry[] entries) => + new TestEntry() { Name = name, Children = entries }; + + public static TestEntry File(string name, string path = null) => + new TestEntry() { Name = name, ResourcePath = path ?? name }; + + public XElement ToXElement() => IsFile ? + new XElement("File", new XAttribute("Name", Name), new XElement("ResourcePath", ResourcePath)) : + new XElement("Directory", new XAttribute("Name", Name), Children.Select(c => c.ToXElement())); + + public IEnumerable GetFiles() + { + if (IsFile) + { + return Enumerable.Empty(); + } + + var files = Children.Where(c => c.IsFile).ToArray(); + var otherFiles = Children.Where(c => !c.IsFile).SelectMany(d => d.GetFiles()).ToArray(); + + return files.Concat(otherFiles).ToArray(); + } + + } +} diff --git a/src/FileProviders/Embedded/test/ManifestEmbeddedFileProviderTests.cs b/src/FileProviders/Embedded/test/ManifestEmbeddedFileProviderTests.cs new file mode 100644 index 0000000000..a973c9df00 --- /dev/null +++ b/src/FileProviders/Embedded/test/ManifestEmbeddedFileProviderTests.cs @@ -0,0 +1,428 @@ +// 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.IO; +using System.Linq; +using Microsoft.Extensions.FileProviders.Embedded.Manifest; +using Xunit; + +namespace Microsoft.Extensions.FileProviders +{ + public class ManifestEmbeddedFileProviderTests + { + [Fact] + public void GetFileInfo_CanResolveSimpleFiles() + { + // Arrange + var assembly = new TestAssembly( + TestEntry.Directory("unused", + TestEntry.File("jquery.validate.js"), + TestEntry.File("jquery.min.js"), + TestEntry.File("site.css"))); + + // Act + var provider = new ManifestEmbeddedFileProvider(assembly); + + // Assert + var jqueryValidate = provider.GetFileInfo("jquery.validate.js"); + Assert.True(jqueryValidate.Exists); + Assert.False(jqueryValidate.IsDirectory); + Assert.Equal("jquery.validate.js", jqueryValidate.Name); + Assert.Null(jqueryValidate.PhysicalPath); + Assert.Equal(0, jqueryValidate.Length); + + var jqueryMin = provider.GetFileInfo("jquery.min.js"); + Assert.True(jqueryMin.Exists); + Assert.False(jqueryMin.IsDirectory); + Assert.Equal("jquery.min.js", jqueryMin.Name); + Assert.Null(jqueryMin.PhysicalPath); + Assert.Equal(0, jqueryMin.Length); + + var siteCss = provider.GetFileInfo("site.css"); + Assert.True(siteCss.Exists); + Assert.False(siteCss.IsDirectory); + Assert.Equal("site.css", siteCss.Name); + Assert.Null(siteCss.PhysicalPath); + Assert.Equal(0, siteCss.Length); + } + + [Fact] + public void GetFileInfo_CanResolveFilesInsideAFolder() + { + // Arrange + var assembly = new TestAssembly( + TestEntry.Directory("unused", + TestEntry.Directory("wwwroot", + TestEntry.File("jquery.validate.js"), + TestEntry.File("jquery.min.js"), + TestEntry.File("site.css")))); + + // Act + var provider = new ManifestEmbeddedFileProvider(assembly); + + // Assert + var jqueryValidate = provider.GetFileInfo(Path.Combine("wwwroot", "jquery.validate.js")); + Assert.True(jqueryValidate.Exists); + Assert.False(jqueryValidate.IsDirectory); + Assert.Equal("jquery.validate.js", jqueryValidate.Name); + Assert.Null(jqueryValidate.PhysicalPath); + Assert.Equal(0, jqueryValidate.Length); + + var jqueryMin = provider.GetFileInfo(Path.Combine("wwwroot", "jquery.min.js")); + Assert.True(jqueryMin.Exists); + Assert.False(jqueryMin.IsDirectory); + Assert.Equal("jquery.min.js", jqueryMin.Name); + Assert.Null(jqueryMin.PhysicalPath); + Assert.Equal(0, jqueryMin.Length); + + var siteCss = provider.GetFileInfo(Path.Combine("wwwroot", "site.css")); + Assert.True(siteCss.Exists); + Assert.False(siteCss.IsDirectory); + Assert.Equal("site.css", siteCss.Name); + Assert.Null(siteCss.PhysicalPath); + Assert.Equal(0, siteCss.Length); + } + + [Fact] + public void GetFileInfo_ResolveNonExistingFile_ReturnsNotFoundFileInfo() + { + // Arrange + var assembly = new TestAssembly( + TestEntry.Directory("unused", + TestEntry.Directory("wwwroot", + TestEntry.File("jquery.validate.js"), + TestEntry.File("jquery.min.js"), + TestEntry.File("site.css")))); + + var provider = new ManifestEmbeddedFileProvider(assembly); + + // Act + var file = provider.GetFileInfo("some/non/existing/file.txt"); + + // Assert + Assert.IsType(file); + } + + [Fact] + public void GetFileInfo_ResolveNonExistingDirectory_ReturnsNotFoundFileInfo() + { + // Arrange + var assembly = new TestAssembly( + TestEntry.Directory("unused", + TestEntry.Directory("wwwroot", + TestEntry.File("jquery.validate.js"), + TestEntry.File("jquery.min.js"), + TestEntry.File("site.css")))); + + var provider = new ManifestEmbeddedFileProvider(assembly); + + // Act + var file = provider.GetFileInfo("some"); + + // Assert + Assert.IsType(file); + } + + [Fact] + public void GetFileInfo_ResolveExistingDirectory_ReturnsNotFoundFileInfo() + { + // Arrange + var assembly = new TestAssembly( + TestEntry.Directory("unused", + TestEntry.Directory("wwwroot", + TestEntry.File("jquery.validate.js"), + TestEntry.File("jquery.min.js"), + TestEntry.File("site.css")))); + + var provider = new ManifestEmbeddedFileProvider(assembly); + + // Act + var file = provider.GetFileInfo("wwwroot"); + + // Assert + Assert.IsType(file); + } + + [Theory] + [InlineData("WWWROOT", "JQUERY.VALIDATE.JS")] + [InlineData("WwWRoOT", "JQuERY.VALiDATE.js")] + public void GetFileInfo_ResolvesFiles_WithDifferentCasing(string folder, string file) + { + // Arrange + var assembly = new TestAssembly( + TestEntry.Directory("unused", + TestEntry.Directory("wwwroot", + TestEntry.File("jquery.validate.js"), + TestEntry.File("jquery.min.js"), + TestEntry.File("site.css")))); + + // Act + var provider = new ManifestEmbeddedFileProvider(assembly); + + // Assert + var jqueryValidate = provider.GetFileInfo(Path.Combine(folder, file)); + Assert.True(jqueryValidate.Exists); + Assert.False(jqueryValidate.IsDirectory); + Assert.Equal("jquery.validate.js", jqueryValidate.Name); + Assert.Null(jqueryValidate.PhysicalPath); + Assert.Equal(0, jqueryValidate.Length); + } + + [Fact] + public void GetFileInfo_AllowsLeadingDots_OnThePath() + { + // Arrange + var assembly = new TestAssembly( + TestEntry.Directory("unused", + TestEntry.Directory("wwwroot", + TestEntry.File("jquery.validate.js"), + TestEntry.File("jquery.min.js"), + TestEntry.File("site.css")))); + + // Act + var provider = new ManifestEmbeddedFileProvider(assembly); + + // Assert + var jqueryValidate = provider.GetFileInfo(Path.Combine(".", "wwwroot", "jquery.validate.js")); + Assert.True(jqueryValidate.Exists); + Assert.False(jqueryValidate.IsDirectory); + Assert.Equal("jquery.validate.js", jqueryValidate.Name); + Assert.Null(jqueryValidate.PhysicalPath); + Assert.Equal(0, jqueryValidate.Length); + } + + [Fact] + public void GetFileInfo_EscapingFromTheRootFolder_ReturnsNotFound() + { + // Arrange + var assembly = new TestAssembly( + TestEntry.Directory("unused", + TestEntry.Directory("wwwroot", + TestEntry.File("jquery.validate.js"), + TestEntry.File("jquery.min.js"), + TestEntry.File("site.css")))); + + // Act + var provider = new ManifestEmbeddedFileProvider(assembly); + + // Assert + var jqueryValidate = provider.GetFileInfo(Path.Combine("..", "wwwroot", "jquery.validate.js")); + Assert.IsType(jqueryValidate); + } + + [Theory] + [InlineData("wwwroot/jquery?validate.js")] + [InlineData("wwwroot/jquery*validate.js")] + [InlineData("wwwroot/jquery:validate.js")] + [InlineData("wwwroot/jqueryvalidate.js")] + [InlineData("wwwroot/jquery\0validate.js")] + public void GetFileInfo_ReturnsNotFoundfileInfo_ForPathsWithInvalidCharacters(string path) + { + // Arrange + var assembly = new TestAssembly( + TestEntry.Directory("unused", + TestEntry.Directory("wwwroot", + TestEntry.File("jquery.validate.js"), + TestEntry.File("jquery.min.js"), + TestEntry.File("site.css")))); + + // Act + var provider = new ManifestEmbeddedFileProvider(assembly); + + // Assert + var file = provider.GetFileInfo(path); + Assert.IsType(file); + Assert.Equal(path, file.Name); + } + + [Fact] + public void GetDirectoryContents_CanEnumerateExistingFolders() + { + // Arrange + var assembly = new TestAssembly( + TestEntry.Directory("unused", + TestEntry.Directory("wwwroot", + TestEntry.File("jquery.validate.js"), + TestEntry.File("jquery.min.js"), + TestEntry.File("site.css")))); + + var provider = new ManifestEmbeddedFileProvider(assembly); + + var expectedContents = new[] + { + CreateTestFileInfo("jquery.validate.js"), + CreateTestFileInfo("jquery.min.js"), + CreateTestFileInfo("site.css") + }; + + // Act + var contents = provider.GetDirectoryContents("wwwroot").ToArray(); + + // Assert + Assert.Equal(expectedContents, contents, FileInfoComparer.Instance); + } + + [Fact] + public void GetDirectoryContents_EnumeratesOnlyAGivenLevel() + { + // Arrange + var assembly = new TestAssembly( + TestEntry.Directory("unused", + TestEntry.Directory("wwwroot", + TestEntry.File("jquery.validate.js"), + TestEntry.File("jquery.min.js"), + TestEntry.File("site.css")))); + + var provider = new ManifestEmbeddedFileProvider(assembly); + + var expectedContents = new[] + { + CreateTestFileInfo("wwwroot", isDirectory: true) + }; + + // Act + var contents = provider.GetDirectoryContents(".").ToArray(); + + // Assert + Assert.Equal(expectedContents, contents, FileInfoComparer.Instance); + } + + [Fact] + public void GetDirectoryContents_EnumeratesFilesAndDirectoriesOnAGivenPath() + { + // Arrange + var assembly = new TestAssembly( + TestEntry.Directory("unused", + TestEntry.Directory("wwwroot"), + TestEntry.File("site.css"))); + + var provider = new ManifestEmbeddedFileProvider(assembly); + + var expectedContents = new[] + { + CreateTestFileInfo("wwwroot", isDirectory: true), + CreateTestFileInfo("site.css") + }; + + // Act + var contents = provider.GetDirectoryContents(".").ToArray(); + + // Assert + Assert.Equal(expectedContents, contents, FileInfoComparer.Instance); + } + + [Fact] + public void GetDirectoryContents_ReturnsNoEntries_ForNonExistingDirectories() + { + // Arrange + var assembly = new TestAssembly( + TestEntry.Directory("unused", + TestEntry.Directory("wwwroot"), + TestEntry.File("site.css"))); + + var provider = new ManifestEmbeddedFileProvider(assembly); + + // Act + var contents = provider.GetDirectoryContents("non-existing"); + + // Assert + Assert.IsType(contents); + } + + [Fact] + public void GetDirectoryContents_ReturnsNoEntries_ForFilePaths() + { + // Arrange + var assembly = new TestAssembly( + TestEntry.Directory("unused", + TestEntry.Directory("wwwroot"), + TestEntry.File("site.css"))); + + var provider = new ManifestEmbeddedFileProvider(assembly); + + // Act + var contents = provider.GetDirectoryContents("site.css"); + + // Assert + Assert.IsType(contents); + } + + [Theory] + [InlineData("wwwro*t")] + [InlineData("wwwro?t")] + [InlineData("wwwro:t")] + [InlineData("wwwrot")] + [InlineData("wwwro\0t")] + public void GetDirectoryContents_ReturnsNotFoundDirectoryContents_ForPathsWithInvalidCharacters(string path) + { + // Arrange + var assembly = new TestAssembly( + TestEntry.Directory("unused", + TestEntry.Directory("wwwroot", + TestEntry.File("jquery.validate.js"), + TestEntry.File("jquery.min.js"), + TestEntry.File("site.css")))); + + // Act + var provider = new ManifestEmbeddedFileProvider(assembly); + + // Assert + var directory = provider.GetDirectoryContents(path); + Assert.IsType(directory); + } + + [Fact] + public void Contructor_CanScopeManifestToAFolder() + { + // Arrange + var assembly = new TestAssembly( + TestEntry.Directory("unused", + TestEntry.Directory("wwwroot", + TestEntry.File("jquery.validate.js")), + TestEntry.File("site.css"))); + + var provider = new ManifestEmbeddedFileProvider(assembly); + var scopedProvider = new ManifestEmbeddedFileProvider(assembly, provider.Manifest.Scope("wwwroot"), DateTimeOffset.UtcNow); + + // Act + var jqueryValidate = scopedProvider.GetFileInfo("jquery.validate.js"); + + // Assert + Assert.True(jqueryValidate.Exists); + Assert.False(jqueryValidate.IsDirectory); + Assert.Equal("jquery.validate.js", jqueryValidate.Name); + Assert.Null(jqueryValidate.PhysicalPath); + Assert.Equal(0, jqueryValidate.Length); + } + + [Theory] + [InlineData("wwwroot/jquery.validate.js")] + [InlineData("../wwwroot/jquery.validate.js")] + [InlineData("site.css")] + [InlineData("../site.css")] + public void ScopedFileProvider_DoesNotReturnFilesOutOfScope(string path) + { + // Arrange + var assembly = new TestAssembly( + TestEntry.Directory("unused", + TestEntry.Directory("wwwroot", + TestEntry.File("jquery.validate.js")), + TestEntry.File("site.css"))); + + var provider = new ManifestEmbeddedFileProvider(assembly); + var scopedProvider = new ManifestEmbeddedFileProvider(assembly, provider.Manifest.Scope("wwwroot"), DateTimeOffset.UtcNow); + + // Act + var jqueryValidate = scopedProvider.GetFileInfo(path); + + // Assert + Assert.IsType(jqueryValidate); + } + + private IFileInfo CreateTestFileInfo(string name, bool isDirectory = false) => + new TestFileInfo(name, isDirectory); + } +} diff --git a/src/FileProviders/Embedded/test/Microsoft.Extensions.FileProviders.Embedded.Tests.csproj b/src/FileProviders/Embedded/test/Microsoft.Extensions.FileProviders.Embedded.Tests.csproj new file mode 100644 index 0000000000..8703c9c508 --- /dev/null +++ b/src/FileProviders/Embedded/test/Microsoft.Extensions.FileProviders.Embedded.Tests.csproj @@ -0,0 +1,15 @@ + + + + $(StandardTestTfms) + + + + + + + + + + + diff --git a/src/FileProviders/Embedded/test/Resources/File.txt b/src/FileProviders/Embedded/test/Resources/File.txt new file mode 100644 index 0000000000..d498f37006 --- /dev/null +++ b/src/FileProviders/Embedded/test/Resources/File.txt @@ -0,0 +1 @@ +Resources-Hello \ No newline at end of file diff --git a/src/FileProviders/Embedded/test/Resources/ResourcesInSubdirectory/File3.txt b/src/FileProviders/Embedded/test/Resources/ResourcesInSubdirectory/File3.txt new file mode 100644 index 0000000000..8651decea6 --- /dev/null +++ b/src/FileProviders/Embedded/test/Resources/ResourcesInSubdirectory/File3.txt @@ -0,0 +1 @@ +Hello3 diff --git a/src/FileProviders/Embedded/test/TestAssembly.cs b/src/FileProviders/Embedded/test/TestAssembly.cs new file mode 100644 index 0000000000..4917bd60ed --- /dev/null +++ b/src/FileProviders/Embedded/test/TestAssembly.cs @@ -0,0 +1,69 @@ +// 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.IO; +using System.Linq; +using System.Reflection; +using System.Text; +using System.Xml; +using System.Xml.Linq; +using Microsoft.Extensions.FileProviders.Embedded.Manifest; + +namespace Microsoft.Extensions.FileProviders +{ + internal class TestAssembly : Assembly + { + public TestAssembly(string manifest, string manifestName = "Microsoft.Extensions.FileProviders.Embedded.Manifest.xml") + { + ManifestStream = new MemoryStream(); + using (var writer = new StreamWriter(ManifestStream, Encoding.UTF8, 1024, leaveOpen: true)) + { + writer.Write(manifest); + } + + ManifestStream.Seek(0, SeekOrigin.Begin); + ManifestName = manifestName; + } + + public TestAssembly(TestEntry entry, string manifestName = "Microsoft.Extensions.FileProviders.Embedded.Manifest.xml") + { + ManifestName = manifestName; + + var manifest = new XDocument( + new XDeclaration("1.0", "utf-8", "yes"), + new XElement("Manifest", + new XElement("ManifestVersion", "1.0"), + new XElement("FileSystem", entry.Children.Select(c => c.ToXElement())))); + + ManifestStream = new MemoryStream(); + using (var writer = XmlWriter.Create(ManifestStream, new XmlWriterSettings { CloseOutput = false })) + { + manifest.WriteTo(writer); + } + + ManifestStream.Seek(0, SeekOrigin.Begin); + Files = entry.GetFiles().Select(f => f.ResourcePath).ToArray(); + } + + public string ManifestName { get; } + public MemoryStream ManifestStream { get; private set; } + public string[] Files { get; private set; } + + public override Stream GetManifestResourceStream(string name) + { + if (string.Equals(ManifestName, name)) + { + return ManifestStream; + } + + return Files.Contains(name) ? Stream.Null : null; + } + + public override string Location => null; + + public override AssemblyName GetName() + { + return new AssemblyName("TestAssembly"); + } + } +} diff --git a/src/FileProviders/Embedded/test/TestFileInfo.cs b/src/FileProviders/Embedded/test/TestFileInfo.cs new file mode 100644 index 0000000000..d410a3b5e7 --- /dev/null +++ b/src/FileProviders/Embedded/test/TestFileInfo.cs @@ -0,0 +1,34 @@ +// 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.IO; + +namespace Microsoft.Extensions.FileProviders +{ + internal class TestFileInfo : IFileInfo + { + private readonly string _name; + private readonly bool _isDirectory; + + public TestFileInfo(string name, bool isDirectory) + { + _name = name; + _isDirectory = isDirectory; + } + + public bool Exists => true; + + public long Length => _isDirectory ? -1 : 0; + + public string PhysicalPath => null; + + public string Name => _name; + + public DateTimeOffset LastModified => throw new NotImplementedException(); + + public bool IsDirectory => _isDirectory; + + public Stream CreateReadStream() => Stream.Null; + } +} diff --git a/src/FileProviders/Embedded/test/sub/File2.txt b/src/FileProviders/Embedded/test/sub/File2.txt new file mode 100644 index 0000000000..e8ecfad884 --- /dev/null +++ b/src/FileProviders/Embedded/test/sub/File2.txt @@ -0,0 +1 @@ +Hello2 diff --git a/src/FileProviders/Embedded/test/sub/dir/File3.txt b/src/FileProviders/Embedded/test/sub/dir/File3.txt new file mode 100644 index 0000000000000000000000000000000000000000..49cc8ef0e116cef009fe0bd72473a964bbd07f9b GIT binary patch literal 6 NcmezWkC%aq0RRg=0u=xN literal 0 HcmV?d00001 diff --git a/src/FileProviders/Manifest.MSBuildTask/src/EmbeddedItem.cs b/src/FileProviders/Manifest.MSBuildTask/src/EmbeddedItem.cs new file mode 100644 index 0000000000..c2dbd58ed2 --- /dev/null +++ b/src/FileProviders/Manifest.MSBuildTask/src/EmbeddedItem.cs @@ -0,0 +1,21 @@ +// 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; + +namespace Microsoft.Extensions.FileProviders.Embedded.Manifest.Task +{ + public class EmbeddedItem : IEquatable + { + public string ManifestFilePath { get; set; } + + public string AssemblyResourceName { get; set; } + + public bool Equals(EmbeddedItem other) => + string.Equals(ManifestFilePath, other?.ManifestFilePath, StringComparison.Ordinal) && + string.Equals(AssemblyResourceName, other?.AssemblyResourceName, StringComparison.Ordinal); + + public override bool Equals(object obj) => Equals(obj as EmbeddedItem); + public override int GetHashCode() => ManifestFilePath.GetHashCode() ^ AssemblyResourceName.GetHashCode(); + } +} diff --git a/src/FileProviders/Manifest.MSBuildTask/src/Entry.cs b/src/FileProviders/Manifest.MSBuildTask/src/Entry.cs new file mode 100644 index 0000000000..40c815fde4 --- /dev/null +++ b/src/FileProviders/Manifest.MSBuildTask/src/Entry.cs @@ -0,0 +1,120 @@ +// 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.Collections.Generic; +using System.Diagnostics; + +namespace Microsoft.Extensions.FileProviders.Embedded.Manifest.Task.Internal +{ + /// + /// This type is for internal uses only and is not meant to be consumed by any other library. + /// + [DebuggerDisplay("{Name,nq}")] + public class Entry : IEquatable + { + public bool IsFile { get; private set; } + + public string Name { get; private set; } + + public string AssemblyResourceName { get; private set; } + + public ISet Children { get; } = new SortedSet(NameComparer.Instance); + + public static Entry Directory(string name) => + new Entry { Name = name }; + + public static Entry File(string name, string assemblyResourceName) => + new Entry { Name = name, AssemblyResourceName = assemblyResourceName, IsFile = true }; + + internal void AddChild(Entry child) + { + if (IsFile) + { + throw new InvalidOperationException("Tried to add children to a file."); + } + + if (Children.Contains(child)) + { + throw new InvalidOperationException($"An item with the name '{child.Name}' already exists."); + } + + Children.Add(child); + } + + internal Entry GetDirectory(string currentSegment) + { + if (IsFile) + { + throw new InvalidOperationException("Tried to get a directory from a file."); + } + + foreach (var child in Children) + { + if (child.HasName(currentSegment)) + { + if (child.IsFile) + { + throw new InvalidOperationException("Tried to find a directory but found a file instead"); + } + else + { + return child; + } + } + } + + return null; + } + + public bool Equals(Entry other) + { + if (other == null || !other.HasName(Name) || other.IsFile != IsFile) + { + return false; + } + + if (IsFile) + { + return string.Equals(other.AssemblyResourceName, AssemblyResourceName, StringComparison.Ordinal); + } + else + { + return SameChildren(Children, other.Children); + } + } + + private bool HasName(string currentSegment) + { + return string.Equals(Name, currentSegment, StringComparison.Ordinal); + } + + private bool SameChildren(ISet left, ISet right) + { + if (left.Count != right.Count) + { + return false; + } + + var le = left.GetEnumerator(); + var re = right.GetEnumerator(); + while (le.MoveNext() && re.MoveNext()) + { + if (!le.Current.Equals(re.Current)) + { + return false; + } + } + + return true; + } + + private class NameComparer : IComparer + { + public static NameComparer Instance { get; } = new NameComparer(); + + public int Compare(Entry x, Entry y) => + string.Compare(x?.Name, y?.Name, StringComparison.Ordinal); + } + } +} diff --git a/src/FileProviders/Manifest.MSBuildTask/src/GenerateEmbeddedResourcesManifest.cs b/src/FileProviders/Manifest.MSBuildTask/src/GenerateEmbeddedResourcesManifest.cs new file mode 100644 index 0000000000..3a62d3d5e3 --- /dev/null +++ b/src/FileProviders/Manifest.MSBuildTask/src/GenerateEmbeddedResourcesManifest.cs @@ -0,0 +1,104 @@ +// 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.IO; +using System.Linq; +using System.Text; +using System.Xml; +using Microsoft.Build.Framework; + +namespace Microsoft.Extensions.FileProviders.Embedded.Manifest.Task +{ + /// + /// Task for generating a manifest file out of the embedded resources in an + /// assembly. + /// + public class GenerateEmbeddedResourcesManifest : Microsoft.Build.Utilities.Task + { + private const string LogicalName = "LogicalName"; + private const string ManifestResourceName = "ManifestResourceName"; + private const string TargetPath = "TargetPath"; + + [Required] + public ITaskItem[] EmbeddedFiles { get; set; } + + [Required] + public string ManifestFile { get; set; } + + /// + public override bool Execute() + { + var processedItems = CreateEmbeddedItems(EmbeddedFiles); + + var manifest = BuildManifest(processedItems); + + var document = manifest.ToXmlDocument(); + + var settings = new XmlWriterSettings() + { + Encoding = Encoding.UTF8, + CloseOutput = true + }; + + using (var xmlWriter = GetXmlWriter(settings)) + { + document.WriteTo(xmlWriter); + } + + return true; + } + + protected virtual XmlWriter GetXmlWriter(XmlWriterSettings settings) + { + if (settings == null) + { + throw new ArgumentNullException(nameof(settings)); + } + + var fileStream = new FileStream(ManifestFile, FileMode.Create); + return XmlWriter.Create(fileStream, settings); + } + + public EmbeddedItem[] CreateEmbeddedItems(params ITaskItem[] items) + { + if (items == null) + { + throw new ArgumentNullException(nameof(items)); + } + + return items.Select(er => new EmbeddedItem + { + ManifestFilePath = GetManifestPath(er), + AssemblyResourceName = GetAssemblyResourceName(er) + }).ToArray(); + } + + public Manifest BuildManifest(EmbeddedItem[] processedItems) + { + if (processedItems == null) + { + throw new ArgumentNullException(nameof(processedItems)); + } + + var manifest = new Manifest(); + foreach (var item in processedItems) + { + manifest.AddElement(item.ManifestFilePath, item.AssemblyResourceName); + } + + return manifest; + } + + private string GetManifestPath(ITaskItem taskItem) => string.Equals(taskItem.GetMetadata(LogicalName), taskItem.GetMetadata(ManifestResourceName)) ? + taskItem.GetMetadata(TargetPath) : + NormalizePath(taskItem.GetMetadata(LogicalName)); + + private string GetAssemblyResourceName(ITaskItem taskItem) => string.Equals(taskItem.GetMetadata(LogicalName), taskItem.GetMetadata(ManifestResourceName)) ? + taskItem.GetMetadata(ManifestResourceName) : + taskItem.GetMetadata(LogicalName); + + private string NormalizePath(string path) => Path.DirectorySeparatorChar == '\\' ? + path.Replace("/", "\\") : path.Replace("\\", "/"); + } +} diff --git a/src/FileProviders/Manifest.MSBuildTask/src/Manifest.cs b/src/FileProviders/Manifest.MSBuildTask/src/Manifest.cs new file mode 100644 index 0000000000..86e99477ff --- /dev/null +++ b/src/FileProviders/Manifest.MSBuildTask/src/Manifest.cs @@ -0,0 +1,85 @@ +// 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.Collections.Generic; +using System.IO; +using System.Linq; +using System.Xml.Linq; +using Microsoft.Extensions.FileProviders.Embedded.Manifest.Task.Internal; + +namespace Microsoft.Extensions.FileProviders.Embedded.Manifest.Task +{ + public class Manifest + { + public Entry Root { get; set; } = Entry.Directory(""); + + public void AddElement(string originalPath, string assemblyResourceName) + { + if (originalPath == null) + { + throw new System.ArgumentNullException(nameof(originalPath)); + } + + if (assemblyResourceName == null) + { + throw new System.ArgumentNullException(nameof(assemblyResourceName)); + } + + var paths = originalPath.Split(Path.DirectorySeparatorChar); + var current = Root; + for (int i = 0; i < paths.Length - 1; i++) + { + var currentSegment = paths[i]; + var next = current.GetDirectory(currentSegment); + if (next == null) + { + next = Entry.Directory(currentSegment); + current.AddChild(next); + } + current = next; + } + + current.AddChild(Entry.File(paths[paths.Length - 1], assemblyResourceName)); + } + + public XDocument ToXmlDocument() + { + var document = new XDocument(new XDeclaration("1.0", "utf-8", "yes")); + var root = new XElement(ElementNames.Root, + new XElement(ElementNames.ManifestVersion, "1.0"), + new XElement(ElementNames.FileSystem, + Root.Children.Select(e => BuildNode(e)))); + + document.Add(root); + + return document; + } + + private XElement BuildNode(Entry entry) + { + if (entry.IsFile) + { + return new XElement(ElementNames.File, + new XAttribute(ElementNames.Name, entry.Name), + new XElement(ElementNames.ResourcePath, entry.AssemblyResourceName)); + } + else + { + var directory = new XElement(ElementNames.Directory, new XAttribute(ElementNames.Name, entry.Name)); + directory.Add(entry.Children.Select(c => BuildNode(c))); + return directory; + } + } + + private class ElementNames + { + public static readonly string Directory = "Directory"; + public static readonly string Name = "Name"; + public static readonly string FileSystem = "FileSystem"; + public static readonly string Root = "Manifest"; + public static readonly string File = "File"; + public static readonly string ResourcePath = "ResourcePath"; + public static readonly string ManifestVersion = "ManifestVersion"; + } + } +} diff --git a/src/FileProviders/Manifest.MSBuildTask/src/Microsoft.Extensions.FileProviders.Embedded.Manifest.Task.csproj b/src/FileProviders/Manifest.MSBuildTask/src/Microsoft.Extensions.FileProviders.Embedded.Manifest.Task.csproj new file mode 100644 index 0000000000..70784650de --- /dev/null +++ b/src/FileProviders/Manifest.MSBuildTask/src/Microsoft.Extensions.FileProviders.Embedded.Manifest.Task.csproj @@ -0,0 +1,25 @@ + + + + MSBuild task to generate a manifest that can be used by Microsoft.Extensions.FileProviders.Embedded to preserve + metadata of the files embedded in the assembly at compilation time. + netstandard1.5;net461 + false + false + false + false + + + + + + + + + + + + + + + diff --git a/src/FileProviders/Manifest.MSBuildTask/test/GenerateEmbeddedResourcesManifestTest.cs b/src/FileProviders/Manifest.MSBuildTask/test/GenerateEmbeddedResourcesManifestTest.cs new file mode 100644 index 0000000000..c7285913af --- /dev/null +++ b/src/FileProviders/Manifest.MSBuildTask/test/GenerateEmbeddedResourcesManifestTest.cs @@ -0,0 +1,388 @@ +// 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.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; +using System.Xml; +using System.Xml.Linq; +using Microsoft.Build.Framework; +using Microsoft.Build.Utilities; +using Microsoft.Extensions.FileProviders.Embedded.Manifest.Task.Internal; +using Xunit; + +namespace Microsoft.Extensions.FileProviders.Embedded.Manifest.Task +{ + public class GenerateEmbeddedResourcesManifestTest + { + [Fact] + public void CreateEmbeddedItems_MapsMetadataFromEmbeddedResources_UsesTheTargetPath() + { + // Arrange + var task = new TestGenerateEmbeddedResourcesManifest(); + var embeddedFiles = CreateEmbeddedResource( + CreateMetadata(@"lib\js\jquery.validate.js")); + + var expectedItems = new[] + { + CreateEmbeddedItem(@"lib\js\jquery.validate.js","lib.js.jquery.validate.js") + }; + + // Act + var embeddedItems = task.CreateEmbeddedItems(embeddedFiles); + + // Assert + Assert.Equal(expectedItems, embeddedItems); + } + + [Fact] + public void CreateEmbeddedItems_MapsMetadataFromEmbeddedResources_WithLogicalName() + { + // Arrange + var task = new TestGenerateEmbeddedResourcesManifest(); + var DirectorySeparator = (Path.DirectorySeparatorChar == '\\' ? '/' : '\\'); + var embeddedFiles = CreateEmbeddedResource( + CreateMetadata("site.css", null, "site.css"), + CreateMetadata("lib/jquery.validate.js", null, $"dist{DirectorySeparator}jquery.validate.js")); + + var expectedItems = new[] + { + CreateEmbeddedItem("site.css","site.css"), + CreateEmbeddedItem(Path.Combine("dist","jquery.validate.js"),$"dist{DirectorySeparator}jquery.validate.js") + }; + + // Act + var embeddedItems = task.CreateEmbeddedItems(embeddedFiles); + + // Assert + Assert.Equal(expectedItems, embeddedItems); + } + + [Fact] + public void BuildManifest_CanCreatesManifest_ForTopLevelFiles() + { + // Arrange + var task = new TestGenerateEmbeddedResourcesManifest(); + var embeddedFiles = CreateEmbeddedResource( + CreateMetadata("jquery.validate.js"), + CreateMetadata("jquery.min.js"), + CreateMetadata("Site.css")); + + var manifestFiles = task.CreateEmbeddedItems(embeddedFiles); + + var expectedManifest = new Manifest() + { + Root = Entry.Directory("").AddRange( + Entry.File("jquery.validate.js", "jquery.validate.js"), + Entry.File("jquery.min.js", "jquery.min.js"), + Entry.File("Site.css", "Site.css")) + }; + + // Act + var manifest = task.BuildManifest(manifestFiles); + + // Assert + Assert.Equal(expectedManifest, manifest, ManifestComparer.Instance); + } + + [Fact] + public void BuildManifest_CanCreatesManifest_ForFilesWithinAFolder() + { + // Arrange + var task = new TestGenerateEmbeddedResourcesManifest(); + var embeddedFiles = CreateEmbeddedResource( + CreateMetadata(Path.Combine("wwwroot", "js", "jquery.validate.js")), + CreateMetadata(Path.Combine("wwwroot", "js", "jquery.min.js")), + CreateMetadata(Path.Combine("wwwroot", "css", "Site.css")), + CreateMetadata(Path.Combine("Areas", "Identity", "Views", "Account", "Index.cshtml"))); + + var manifestFiles = task.CreateEmbeddedItems(embeddedFiles); + + var expectedManifest = new Manifest() + { + Root = Entry.Directory("").AddRange( + Entry.Directory("wwwroot").AddRange( + Entry.Directory("js").AddRange( + Entry.File("jquery.validate.js", "wwwroot.js.jquery.validate.js"), + Entry.File("jquery.min.js", "wwwroot.js.jquery.min.js")), + Entry.Directory("css").AddRange( + Entry.File("Site.css", "wwwroot.css.Site.css"))), + Entry.Directory("Areas").AddRange( + Entry.Directory("Identity").AddRange( + Entry.Directory("Views").AddRange( + Entry.Directory("Account").AddRange( + Entry.File("Index.cshtml", "Areas.Identity.Views.Account.Index.cshtml")))))) + }; + + // Act + var manifest = task.BuildManifest(manifestFiles); + + // Assert + Assert.Equal(expectedManifest, manifest, ManifestComparer.Instance); + } + + [Fact] + public void BuildManifest_RespectsEntriesWithLogicalName() + { + // Arrange + var task = new TestGenerateEmbeddedResourcesManifest(); + var embeddedFiles = CreateEmbeddedResource( + CreateMetadata("jquery.validate.js", null, @"wwwroot\lib\js\jquery.validate.js"), + CreateMetadata("jquery.min.js", null, @"wwwroot\lib/js\jquery.min.js"), + CreateMetadata("Site.css", null, "wwwroot/lib/css/site.css")); + var manifestFiles = task.CreateEmbeddedItems(embeddedFiles); + + var expectedManifest = new Manifest() + { + Root = Entry.Directory("").AddRange( + Entry.Directory("wwwroot").AddRange( + Entry.Directory("lib").AddRange( + Entry.Directory("js").AddRange( + Entry.File("jquery.validate.js", @"wwwroot\lib\js\jquery.validate.js"), + Entry.File("jquery.min.js", @"wwwroot\lib/js\jquery.min.js")), + Entry.Directory("css").AddRange( + Entry.File("site.css", "wwwroot/lib/css/site.css"))))) + }; + + // Act + var manifest = task.BuildManifest(manifestFiles); + + // Assert + Assert.Equal(expectedManifest, manifest, ManifestComparer.Instance); + } + + [Fact] + public void BuildManifest_SupportsFilesAndFoldersWithDifferentCasing() + { + // Arrange + var task = new TestGenerateEmbeddedResourcesManifest(); + var embeddedFiles = CreateEmbeddedResource( + CreateMetadata(Path.Combine("A", "b", "c.txt")), + CreateMetadata(Path.Combine("A", "B", "c.txt")), + CreateMetadata(Path.Combine("A", "B", "C.txt")), + CreateMetadata(Path.Combine("A", "b", "C.txt")), + CreateMetadata(Path.Combine("A", "d")), + CreateMetadata(Path.Combine("A", "D", "e.txt"))); + + var manifestFiles = task.CreateEmbeddedItems(embeddedFiles); + + var expectedManifest = new Manifest() + { + Root = Entry.Directory("").AddRange( + Entry.Directory("A").AddRange( + Entry.Directory("b").AddRange( + Entry.File("c.txt", @"A.b.c.txt"), + Entry.File("C.txt", @"A.b.C.txt")), + Entry.Directory("B").AddRange( + Entry.File("c.txt", @"A.B.c.txt"), + Entry.File("C.txt", @"A.B.C.txt")), + Entry.Directory("D").AddRange( + Entry.File("e.txt", "A.D.e.txt")), + Entry.File("d", "A.d"))) + }; + + // Act + var manifest = task.BuildManifest(manifestFiles); + + // Assert + Assert.Equal(expectedManifest, manifest, ManifestComparer.Instance); + } + + [Fact] + public void BuildManifest_ThrowsInvalidOperationException_WhenTryingToAddAFileWithTheSameNameAsAFolder() + { + // Arrange + var task = new TestGenerateEmbeddedResourcesManifest(); + var embeddedFiles = CreateEmbeddedResource( + CreateMetadata(Path.Combine("A", "b", "c.txt")), + CreateMetadata(Path.Combine("A", "b"))); + + var manifestFiles = task.CreateEmbeddedItems(embeddedFiles); + + // Act & Assert + Assert.Throws(() => task.BuildManifest(manifestFiles)); + } + + [Fact] + public void BuildManifest_ThrowsInvalidOperationException_WhenTryingToAddAFolderWithTheSameNameAsAFile() + { + // Arrange + var task = new TestGenerateEmbeddedResourcesManifest(); + var embeddedFiles = CreateEmbeddedResource( + CreateMetadata(Path.Combine("A", "b")), + CreateMetadata(Path.Combine("A", "b", "c.txt"))); + + var manifestFiles = task.CreateEmbeddedItems(embeddedFiles); + + // Act & Assert + Assert.Throws(() => task.BuildManifest(manifestFiles)); + } + + [Fact] + public void ToXmlDocument_GeneratesTheCorrectXmlDocument() + { + // Arrange + var manifest = new Manifest() + { + Root = Entry.Directory("").AddRange( + Entry.Directory("A").AddRange( + Entry.Directory("b").AddRange( + Entry.File("c.txt", @"A.b.c.txt"), + Entry.File("C.txt", @"A.b.C.txt")), + Entry.Directory("B").AddRange( + Entry.File("c.txt", @"A.B.c.txt"), + Entry.File("C.txt", @"A.B.C.txt")), + Entry.Directory("D").AddRange( + Entry.File("e.txt", "A.D.e.txt")), + Entry.File("d", "A.d"))) + }; + + var expectedDocument = new XDocument( + new XDeclaration("1.0", "utf-8", "yes"), + new XElement("Manifest", + new XElement("ManifestVersion", "1.0"), + new XElement("FileSystem", + new XElement("Directory", new XAttribute("Name", "A"), + new XElement("Directory", new XAttribute("Name", "B"), + new XElement("File", new XAttribute("Name", "C.txt"), new XElement("ResourcePath", "A.B.C.txt")), + new XElement("File", new XAttribute("Name", "c.txt"), new XElement("ResourcePath", "A.B.c.txt"))), + new XElement("Directory", new XAttribute("Name", "D"), + new XElement("File", new XAttribute("Name", "e.txt"), new XElement("ResourcePath", "A.D.e.txt"))), + new XElement("Directory", new XAttribute("Name", "b"), + new XElement("File", new XAttribute("Name", "C.txt"), new XElement("ResourcePath", "A.b.C.txt")), + new XElement("File", new XAttribute("Name", "c.txt"), new XElement("ResourcePath", "A.b.c.txt"))), + new XElement("File", new XAttribute("Name", "d"), new XElement("ResourcePath", "A.d")))))); + + // Act + var document = manifest.ToXmlDocument(); + + // Assert + Assert.Equal(expectedDocument.ToString(), document.ToString()); + } + + [Fact] + public void Execute_WritesManifest_ToOutputFile() + { + // Arrange + var task = new TestGenerateEmbeddedResourcesManifest(); + var embeddedFiles = CreateEmbeddedResource( + CreateMetadata(Path.Combine("A", "b", "c.txt")), + CreateMetadata(Path.Combine("A", "B", "c.txt")), + CreateMetadata(Path.Combine("A", "B", "C.txt")), + CreateMetadata(Path.Combine("A", "b", "C.txt")), + CreateMetadata(Path.Combine("A", "d")), + CreateMetadata(Path.Combine("A", "D", "e.txt"))); + + task.EmbeddedFiles = embeddedFiles; + task.ManifestFile = Path.Combine("obj", "debug", "netstandard2.0"); + + var expectedDocument = new XDocument( + new XDeclaration("1.0", "utf-8", "yes"), + new XElement("Manifest", + new XElement("ManifestVersion", "1.0"), + new XElement("FileSystem", + new XElement("Directory", new XAttribute("Name", "A"), + new XElement("Directory", new XAttribute("Name", "B"), + new XElement("File", new XAttribute("Name", "C.txt"), new XElement("ResourcePath", "A.B.C.txt")), + new XElement("File", new XAttribute("Name", "c.txt"), new XElement("ResourcePath", "A.B.c.txt"))), + new XElement("Directory", new XAttribute("Name", "D"), + new XElement("File", new XAttribute("Name", "e.txt"), new XElement("ResourcePath", "A.D.e.txt"))), + new XElement("Directory", new XAttribute("Name", "b"), + new XElement("File", new XAttribute("Name", "C.txt"), new XElement("ResourcePath", "A.b.C.txt")), + new XElement("File", new XAttribute("Name", "c.txt"), new XElement("ResourcePath", "A.b.c.txt"))), + new XElement("File", new XAttribute("Name", "d"), new XElement("ResourcePath", "A.d")))))); + + var expectedOutput = new MemoryStream(); + var writer = XmlWriter.Create(expectedOutput, new XmlWriterSettings { Encoding = Encoding.UTF8 }); + expectedDocument.WriteTo(writer); + writer.Flush(); + expectedOutput.Seek(0, SeekOrigin.Begin); + + // Act + task.Execute(); + + // Assert + task.Output.Seek(0, SeekOrigin.Begin); + using (var expectedReader = new StreamReader(expectedOutput)) + { + using (var reader = new StreamReader(task.Output)) + { + Assert.Equal(expectedReader.ReadToEnd(), reader.ReadToEnd()); + } + } + } + + private EmbeddedItem CreateEmbeddedItem(string manifestPath, string assemblyName) => + new EmbeddedItem + { + ManifestFilePath = manifestPath, + AssemblyResourceName = assemblyName + }; + + + public class TestGenerateEmbeddedResourcesManifest + : GenerateEmbeddedResourcesManifest + { + public TestGenerateEmbeddedResourcesManifest() + : this(new MemoryStream()) + { + } + + public TestGenerateEmbeddedResourcesManifest(Stream output) + { + Output = output; + } + + public Stream Output { get; } + + protected override XmlWriter GetXmlWriter(XmlWriterSettings settings) + { + settings.CloseOutput = false; + return XmlWriter.Create(Output, settings); + } + } + + private ITaskItem[] CreateEmbeddedResource(params IDictionary[] files) => + files.Select(f => CreateTaskItem(f)).ToArray(); + + private ITaskItem CreateTaskItem(IDictionary metadata) + { + var result = new TaskItem(); + foreach (var kvp in metadata) + { + result.SetMetadata(kvp.Key, kvp.Value); + } + + return result; + } + + private static IDictionary + CreateMetadata( + string targetPath, + string manifestResourceName = null, + string logicalName = null) => + new Dictionary + { + ["TargetPath"] = targetPath, + ["ManifestResourceName"] = manifestResourceName ?? targetPath.Replace("/", ".").Replace("\\", "."), + ["LogicalName"] = logicalName ?? targetPath.Replace("/", ".").Replace("\\", "."), + }; + + private class ManifestComparer : IEqualityComparer + { + public static IEqualityComparer Instance { get; } = new ManifestComparer(); + + public bool Equals(Manifest x, Manifest y) + { + return x.Root.Equals(y.Root); + } + + public int GetHashCode(Manifest obj) + { + return obj.Root.GetHashCode(); + } + } + } +} diff --git a/src/FileProviders/Manifest.MSBuildTask/test/Microsoft.Extensions.FileProviders.Embedded.Manifest.Task.Test.csproj b/src/FileProviders/Manifest.MSBuildTask/test/Microsoft.Extensions.FileProviders.Embedded.Manifest.Task.Test.csproj new file mode 100644 index 0000000000..b06f1b2176 --- /dev/null +++ b/src/FileProviders/Manifest.MSBuildTask/test/Microsoft.Extensions.FileProviders.Embedded.Manifest.Task.Test.csproj @@ -0,0 +1,11 @@ + + + + $(StandardTestTfms) + + + + + + + diff --git a/src/FileProviders/Manifest.MSBuildTask/test/SetExtensions.cs b/src/FileProviders/Manifest.MSBuildTask/test/SetExtensions.cs new file mode 100644 index 0000000000..6b2c83a875 --- /dev/null +++ b/src/FileProviders/Manifest.MSBuildTask/test/SetExtensions.cs @@ -0,0 +1,20 @@ +// 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 Microsoft.Extensions.FileProviders.Embedded.Manifest.Task.Internal; + +namespace Microsoft.Extensions.FileProviders.Embedded.Manifest.Task +{ + internal static class SetExtensions + { + public static Entry AddRange(this Entry source, params Entry[] elements) + { + foreach (var element in elements) + { + source.Children.Add(element); + } + + return source; + } + } +} From aee5320ae91471899dc7f4dc08979096c3216612 Mon Sep 17 00:00:00 2001 From: Nate McMaster Date: Fri, 2 Nov 2018 17:25:46 -0700 Subject: [PATCH 0007/1101] Organize packages into different output paths based on whether they are "product" packages or not \n\nCommit migrated from https://github.com/dotnet/extensions/commit/dbb97a2e1376867fa0a64053fb1be7ab8c426540 --- src/ObjectPool/Directory.Build.props | 7 +++++++ src/Shared/CertificateGeneration/Directory.Build.props | 1 - 2 files changed, 7 insertions(+), 1 deletion(-) create mode 100644 src/ObjectPool/Directory.Build.props diff --git a/src/ObjectPool/Directory.Build.props b/src/ObjectPool/Directory.Build.props new file mode 100644 index 0000000000..f25c1d90ce --- /dev/null +++ b/src/ObjectPool/Directory.Build.props @@ -0,0 +1,7 @@ + + + + + true + + diff --git a/src/Shared/CertificateGeneration/Directory.Build.props b/src/Shared/CertificateGeneration/Directory.Build.props index 63355f6faf..f3f6fc4b35 100644 --- a/src/Shared/CertificateGeneration/Directory.Build.props +++ b/src/Shared/CertificateGeneration/Directory.Build.props @@ -1,5 +1,4 @@ - From 376a6c9953e9e71d893e8be8dc36a018eb8929f1 Mon Sep 17 00:00:00 2001 From: Nate McMaster Date: Mon, 5 Nov 2018 13:09:10 -0800 Subject: [PATCH 0008/1101] Merge branch 'release/2.1' into release/2.2 \n\nCommit migrated from https://github.com/dotnet/extensions/commit/2d152804642f41aaeacc09307bcebb065131e75d --- .../src/Microsoft.Extensions.FileProviders.Embedded.csproj | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/FileProviders/Embedded/src/Microsoft.Extensions.FileProviders.Embedded.csproj b/src/FileProviders/Embedded/src/Microsoft.Extensions.FileProviders.Embedded.csproj index d7ca20b469..ec2c10b569 100644 --- a/src/FileProviders/Embedded/src/Microsoft.Extensions.FileProviders.Embedded.csproj +++ b/src/FileProviders/Embedded/src/Microsoft.Extensions.FileProviders.Embedded.csproj @@ -12,6 +12,12 @@ + + + + + + From 360b19ca09c7d4cbdc2d6a551ea0b60dc747e9eb Mon Sep 17 00:00:00 2001 From: Nate McMaster Date: Mon, 5 Nov 2018 13:12:14 -0800 Subject: [PATCH 0009/1101] Merge branch 'release/2.2' \n\nCommit migrated from https://github.com/dotnet/extensions/commit/04bac2f79c39eecba1ea29d035dc9a40e37167d6 --- .../Microsoft.Extensions.FileProviders.Embedded.csproj | 9 ++++++++- .../Microsoft.Extensions.FileProviders.Embedded.nuspec | 4 ++-- .../Microsoft.Extensions.FileProviders.Embedded.props | 2 +- ...ensions.FileProviders.Embedded.Manifest.Task.csproj | 10 ++-------- 4 files changed, 13 insertions(+), 12 deletions(-) diff --git a/src/FileProviders/Embedded/src/Microsoft.Extensions.FileProviders.Embedded.csproj b/src/FileProviders/Embedded/src/Microsoft.Extensions.FileProviders.Embedded.csproj index ec2c10b569..55284793fb 100644 --- a/src/FileProviders/Embedded/src/Microsoft.Extensions.FileProviders.Embedded.csproj +++ b/src/FileProviders/Embedded/src/Microsoft.Extensions.FileProviders.Embedded.csproj @@ -14,7 +14,7 @@ - + @@ -41,10 +41,17 @@ OutputDocumentation=@(DocumentationProjectOutputGroupOutput); +<<<<<<< HEAD:src/FS.Embedded/FS.Embedded.csproj + TaskAssemblyNetStandard=..\FS.Embedded.Manifest.Task\bin\$(Configuration)\netstandard2.0\$(AssemblyName).Manifest.Task.dll; + TaskSymbolNetStandard=..\FS.Embedded.Manifest.Task\bin\$(Configuration)\netstandard2.0\$(AssemblyName).Manifest.Task.pdb; + TaskAssemblyNet461=..\FS.Embedded.Manifest.Task\bin\$(Configuration)\net461\$(AssemblyName).Manifest.Task.dll; + TaskSymbolNet461=..\FS.Embedded.Manifest.Task\bin\$(Configuration)\net461\$(AssemblyName).Manifest.Task.pdb; +======= TaskAssemblyNetStandard=..\..\Manifest.MSBuildTask\src\bin\$(Configuration)\netstandard1.5\$(AssemblyName).Manifest.Task.dll; TaskSymbolNetStandard=..\..\Manifest.MSBuildTask\src\bin\$(Configuration)\netstandard1.5\$(AssemblyName).Manifest.Task.pdb; TaskAssemblyNet461=..\..\Manifest.MSBuildTask\src\bin\$(Configuration)\net461\$(AssemblyName).Manifest.Task.dll; TaskSymbolNet461=..\..\Manifest.MSBuildTask\src\bin\$(Configuration)\net461\$(AssemblyName).Manifest.Task.pdb; +>>>>>>> m22:src/FileProviders/Embedded/src/Microsoft.Extensions.FileProviders.Embedded.csproj diff --git a/src/FileProviders/Embedded/src/Microsoft.Extensions.FileProviders.Embedded.nuspec b/src/FileProviders/Embedded/src/Microsoft.Extensions.FileProviders.Embedded.nuspec index 0cc5ed823a..af7feca8fd 100644 --- a/src/FileProviders/Embedded/src/Microsoft.Extensions.FileProviders.Embedded.nuspec +++ b/src/FileProviders/Embedded/src/Microsoft.Extensions.FileProviders.Embedded.nuspec @@ -25,8 +25,8 @@ - - + + diff --git a/src/FileProviders/Embedded/src/build/netstandard2.0/Microsoft.Extensions.FileProviders.Embedded.props b/src/FileProviders/Embedded/src/build/netstandard2.0/Microsoft.Extensions.FileProviders.Embedded.props index e913e17321..66ea7b5c22 100644 --- a/src/FileProviders/Embedded/src/build/netstandard2.0/Microsoft.Extensions.FileProviders.Embedded.props +++ b/src/FileProviders/Embedded/src/build/netstandard2.0/Microsoft.Extensions.FileProviders.Embedded.props @@ -5,7 +5,7 @@ - <_FileProviderTaskFolder Condition="'$(MSBuildRuntimeType)' == 'Core'">netstandard1.5 + <_FileProviderTaskFolder Condition="'$(MSBuildRuntimeType)' == 'Core'">netstandard2.0 <_FileProviderTaskFolder Condition="'$(MSBuildRuntimeType)' != 'Core'">net461 <_FileProviderTaskAssembly>$(MSBuildThisFileDirectory)..\..\tasks\$(_FileProviderTaskFolder)\Microsoft.Extensions.FileProviders.Embedded.Manifest.Task.dll diff --git a/src/FileProviders/Manifest.MSBuildTask/src/Microsoft.Extensions.FileProviders.Embedded.Manifest.Task.csproj b/src/FileProviders/Manifest.MSBuildTask/src/Microsoft.Extensions.FileProviders.Embedded.Manifest.Task.csproj index 70784650de..018cc98a8e 100644 --- a/src/FileProviders/Manifest.MSBuildTask/src/Microsoft.Extensions.FileProviders.Embedded.Manifest.Task.csproj +++ b/src/FileProviders/Manifest.MSBuildTask/src/Microsoft.Extensions.FileProviders.Embedded.Manifest.Task.csproj @@ -3,23 +3,17 @@ MSBuild task to generate a manifest that can be used by Microsoft.Extensions.FileProviders.Embedded to preserve metadata of the files embedded in the assembly at compilation time. - netstandard1.5;net461 + netstandard2.0 false false false false - + - - - - - - From 09e84b256c4f994781e196e72e92fb8ff28c80d1 Mon Sep 17 00:00:00 2001 From: Nate McMaster Date: Mon, 5 Nov 2018 15:12:53 -0800 Subject: [PATCH 0010/1101] Reorganize source code in preparation to move into aspnet/Extensions Prior to reorganization, this source code was found in https://github.com/aspnet/Configuration/tree/dotnet/extensions@ef29dc86b970893147fd2a27d527f5a907af9fdd \n\nCommit migrated from https://github.com/dotnet/extensions/commit/a8900855a44534fe5ba28bfd28883c0e38d8da07 --- .../Directory.Build.props | 8 + ...eyPerFileConfigurationBuilderExtensions.cs | 42 +++ .../src/KeyPerFileConfigurationProvider.cs | 72 ++++ .../src/KeyPerFileConfigurationSource.cs | 48 +++ ...Extensions.Configuration.KeyPerFile.csproj | 14 + src/Configuration.KeyPerFile/src/README.md | 2 + .../test/KeyPerFileTests.cs | 308 ++++++++++++++++++ ...ions.Configuration.KeyPerFile.Tests.csproj | 11 + 8 files changed, 505 insertions(+) create mode 100644 src/Configuration.KeyPerFile/Directory.Build.props create mode 100644 src/Configuration.KeyPerFile/src/KeyPerFileConfigurationBuilderExtensions.cs create mode 100644 src/Configuration.KeyPerFile/src/KeyPerFileConfigurationProvider.cs create mode 100644 src/Configuration.KeyPerFile/src/KeyPerFileConfigurationSource.cs create mode 100644 src/Configuration.KeyPerFile/src/Microsoft.Extensions.Configuration.KeyPerFile.csproj create mode 100644 src/Configuration.KeyPerFile/src/README.md create mode 100644 src/Configuration.KeyPerFile/test/KeyPerFileTests.cs create mode 100644 src/Configuration.KeyPerFile/test/Microsoft.Extensions.Configuration.KeyPerFile.Tests.csproj diff --git a/src/Configuration.KeyPerFile/Directory.Build.props b/src/Configuration.KeyPerFile/Directory.Build.props new file mode 100644 index 0000000000..2082380096 --- /dev/null +++ b/src/Configuration.KeyPerFile/Directory.Build.props @@ -0,0 +1,8 @@ + + + + + true + configuration + + diff --git a/src/Configuration.KeyPerFile/src/KeyPerFileConfigurationBuilderExtensions.cs b/src/Configuration.KeyPerFile/src/KeyPerFileConfigurationBuilderExtensions.cs new file mode 100644 index 0000000000..435ef9e155 --- /dev/null +++ b/src/Configuration.KeyPerFile/src/KeyPerFileConfigurationBuilderExtensions.cs @@ -0,0 +1,42 @@ +using System; +using System.IO; +using Microsoft.Extensions.Configuration.KeyPerFile; +using Microsoft.Extensions.FileProviders; + +namespace Microsoft.Extensions.Configuration +{ + /// + /// Extension methods for registering with . + /// + public static class KeyPerFileConfigurationBuilderExtensions + { + /// + /// Adds configuration using files from a directory. File names are used as the key, + /// file contents are used as the value. + /// + /// The to add to. + /// The path to the directory. + /// Whether the directory is optional. + /// The . + public static IConfigurationBuilder AddKeyPerFile(this IConfigurationBuilder builder, string directoryPath, bool optional) + => builder.AddKeyPerFile(source => + { + // Only try to set the file provider if its not optional or the directory exists + if (!optional || Directory.Exists(directoryPath)) + { + source.FileProvider = new PhysicalFileProvider(directoryPath); + } + source.Optional = optional; + }); + + /// + /// Adds configuration using files from a directory. File names are used as the key, + /// file contents are used as the value. + /// + /// The to add to. + /// Configures the source. + /// The . + public static IConfigurationBuilder AddKeyPerFile(this IConfigurationBuilder builder, Action configureSource) + => builder.Add(configureSource); + } +} diff --git a/src/Configuration.KeyPerFile/src/KeyPerFileConfigurationProvider.cs b/src/Configuration.KeyPerFile/src/KeyPerFileConfigurationProvider.cs new file mode 100644 index 0000000000..4748895744 --- /dev/null +++ b/src/Configuration.KeyPerFile/src/KeyPerFileConfigurationProvider.cs @@ -0,0 +1,72 @@ +using System; +using System.Collections.Generic; +using System.IO; + +namespace Microsoft.Extensions.Configuration.KeyPerFile +{ + /// + /// A that uses a directory's files as configuration key/values. + /// + public class KeyPerFileConfigurationProvider : ConfigurationProvider + { + KeyPerFileConfigurationSource Source { get; set; } + + /// + /// Initializes a new instance. + /// + /// The settings. + public KeyPerFileConfigurationProvider(KeyPerFileConfigurationSource source) + => Source = source ?? throw new ArgumentNullException(nameof(source)); + + private static string NormalizeKey(string key) + => key.Replace("__", ConfigurationPath.KeyDelimiter); + + private static string TrimNewLine(string value) + => value.EndsWith(Environment.NewLine) + ? value.Substring(0, value.Length - Environment.NewLine.Length) + : value; + + /// + /// Loads the docker secrets. + /// + public override void Load() + { + Data = new Dictionary(StringComparer.OrdinalIgnoreCase); + + if (Source.FileProvider == null) + { + if (Source.Optional) + { + return; + } + else + { + throw new DirectoryNotFoundException("A non-null file provider for the directory is required when this source is not optional."); + } + } + + var directory = Source.FileProvider.GetDirectoryContents("/"); + if (!directory.Exists && !Source.Optional) + { + throw new DirectoryNotFoundException("The root directory for the FileProvider doesn't exist and is not optional."); + } + + foreach (var file in directory) + { + if (file.IsDirectory) + { + continue; + } + + using (var stream = file.CreateReadStream()) + using (var streamReader = new StreamReader(stream)) + { + if (Source.IgnoreCondition == null || !Source.IgnoreCondition(file.Name)) + { + Data.Add(NormalizeKey(file.Name), TrimNewLine(streamReader.ReadToEnd())); + } + } + } + } + } +} diff --git a/src/Configuration.KeyPerFile/src/KeyPerFileConfigurationSource.cs b/src/Configuration.KeyPerFile/src/KeyPerFileConfigurationSource.cs new file mode 100644 index 0000000000..c32e948e82 --- /dev/null +++ b/src/Configuration.KeyPerFile/src/KeyPerFileConfigurationSource.cs @@ -0,0 +1,48 @@ +using System; +using System.IO; +using Microsoft.Extensions.FileProviders; + +namespace Microsoft.Extensions.Configuration.KeyPerFile +{ + /// + /// An used to configure . + /// + public class KeyPerFileConfigurationSource : IConfigurationSource + { + /// + /// Constructor; + /// + public KeyPerFileConfigurationSource() + => IgnoreCondition = s => IgnorePrefix != null && s.StartsWith(IgnorePrefix); + + /// + /// The FileProvider whos root "/" directory files will be used as configuration data. + /// + public IFileProvider FileProvider { get; set; } + + /// + /// Files that start with this prefix will be excluded. + /// Defaults to "ignore.". + /// + public string IgnorePrefix { get; set; } = "ignore."; + + /// + /// Used to determine if a file should be ignored using its name. + /// Defaults to using the IgnorePrefix. + /// + public Func IgnoreCondition { get; set; } + + /// + /// If false, will throw if the directory doesn't exist. + /// + public bool Optional { get; set; } + + /// + /// Builds the for this source. + /// + /// The . + /// A + public IConfigurationProvider Build(IConfigurationBuilder builder) + => new KeyPerFileConfigurationProvider(this); + } +} diff --git a/src/Configuration.KeyPerFile/src/Microsoft.Extensions.Configuration.KeyPerFile.csproj b/src/Configuration.KeyPerFile/src/Microsoft.Extensions.Configuration.KeyPerFile.csproj new file mode 100644 index 0000000000..4eb19f3293 --- /dev/null +++ b/src/Configuration.KeyPerFile/src/Microsoft.Extensions.Configuration.KeyPerFile.csproj @@ -0,0 +1,14 @@ + + + + Configuration provider that uses files in a directory for Microsoft.Extensions.Configuration. + netstandard2.0 + false + + + + + + + + diff --git a/src/Configuration.KeyPerFile/src/README.md b/src/Configuration.KeyPerFile/src/README.md new file mode 100644 index 0000000000..29952e9139 --- /dev/null +++ b/src/Configuration.KeyPerFile/src/README.md @@ -0,0 +1,2 @@ + +This is a configuration provider that uses a directory's files as data. A file's name is the key and the contents are the value. diff --git a/src/Configuration.KeyPerFile/test/KeyPerFileTests.cs b/src/Configuration.KeyPerFile/test/KeyPerFileTests.cs new file mode 100644 index 0000000000..d55387a404 --- /dev/null +++ b/src/Configuration.KeyPerFile/test/KeyPerFileTests.cs @@ -0,0 +1,308 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; +using Microsoft.Extensions.FileProviders; +using Microsoft.Extensions.Primitives; +using Xunit; + +namespace Microsoft.Extensions.Configuration.KeyPerFile.Test +{ + public class KeyPerFileTests + { + [Fact] + public void DoesNotThrowWhenOptionalAndNoSecrets() + { + new ConfigurationBuilder().AddKeyPerFile(o => o.Optional = true).Build(); + } + + [Fact] + public void DoesNotThrowWhenOptionalAndDirectoryDoesntExist() + { + new ConfigurationBuilder().AddKeyPerFile("nonexistent", true).Build(); + } + + [Fact] + public void ThrowsWhenNotOptionalAndDirectoryDoesntExist() + { + var e = Assert.Throws(() => new ConfigurationBuilder().AddKeyPerFile("nonexistent", false).Build()); + Assert.Contains("The directory name", e.Message); + } + + [Fact] + public void CanLoadMultipleSecrets() + { + var testFileProvider = new TestFileProvider( + new TestFile("Secret1", "SecretValue1"), + new TestFile("Secret2", "SecretValue2")); + + var config = new ConfigurationBuilder() + .AddKeyPerFile(o => o.FileProvider = testFileProvider) + .Build(); + + Assert.Equal("SecretValue1", config["Secret1"]); + Assert.Equal("SecretValue2", config["Secret2"]); + } + + [Fact] + public void CanLoadMultipleSecretsWithDirectory() + { + var testFileProvider = new TestFileProvider( + new TestFile("Secret1", "SecretValue1"), + new TestFile("Secret2", "SecretValue2"), + new TestFile("directory")); + + var config = new ConfigurationBuilder() + .AddKeyPerFile(o => o.FileProvider = testFileProvider) + .Build(); + + Assert.Equal("SecretValue1", config["Secret1"]); + Assert.Equal("SecretValue2", config["Secret2"]); + } + + [Fact] + public void CanLoadNestedKeys() + { + var testFileProvider = new TestFileProvider( + new TestFile("Secret0__Secret1__Secret2__Key", "SecretValue2"), + new TestFile("Secret0__Secret1__Key", "SecretValue1"), + new TestFile("Secret0__Key", "SecretValue0")); + + var config = new ConfigurationBuilder() + .AddKeyPerFile(o => o.FileProvider = testFileProvider) + .Build(); + + Assert.Equal("SecretValue0", config["Secret0:Key"]); + Assert.Equal("SecretValue1", config["Secret0:Secret1:Key"]); + Assert.Equal("SecretValue2", config["Secret0:Secret1:Secret2:Key"]); + } + + [Fact] + public void CanIgnoreFilesWithDefault() + { + var testFileProvider = new TestFileProvider( + new TestFile("ignore.Secret0", "SecretValue0"), + new TestFile("ignore.Secret1", "SecretValue1"), + new TestFile("Secret2", "SecretValue2")); + + var config = new ConfigurationBuilder() + .AddKeyPerFile(o => o.FileProvider = testFileProvider) + .Build(); + + Assert.Null(config["ignore.Secret0"]); + Assert.Null(config["ignore.Secret1"]); + Assert.Equal("SecretValue2", config["Secret2"]); + } + + [Fact] + public void CanTurnOffDefaultIgnorePrefixWithCondition() + { + var testFileProvider = new TestFileProvider( + new TestFile("ignore.Secret0", "SecretValue0"), + new TestFile("ignore.Secret1", "SecretValue1"), + new TestFile("Secret2", "SecretValue2")); + + var config = new ConfigurationBuilder() + .AddKeyPerFile(o => + { + o.FileProvider = testFileProvider; + o.IgnoreCondition = null; + }) + .Build(); + + Assert.Equal("SecretValue0", config["ignore.Secret0"]); + Assert.Equal("SecretValue1", config["ignore.Secret1"]); + Assert.Equal("SecretValue2", config["Secret2"]); + } + + [Fact] + public void CanIgnoreAllWithCondition() + { + var testFileProvider = new TestFileProvider( + new TestFile("Secret0", "SecretValue0"), + new TestFile("Secret1", "SecretValue1"), + new TestFile("Secret2", "SecretValue2")); + + var config = new ConfigurationBuilder() + .AddKeyPerFile(o => + { + o.FileProvider = testFileProvider; + o.IgnoreCondition = s => true; + }) + .Build(); + + Assert.Empty(config.AsEnumerable()); + } + + [Fact] + public void CanIgnoreFilesWithCustomIgnore() + { + var testFileProvider = new TestFileProvider( + new TestFile("meSecret0", "SecretValue0"), + new TestFile("meSecret1", "SecretValue1"), + new TestFile("Secret2", "SecretValue2")); + + var config = new ConfigurationBuilder() + .AddKeyPerFile(o => + { + o.FileProvider = testFileProvider; + o.IgnorePrefix = "me"; + }) + .Build(); + + Assert.Null(config["meSecret0"]); + Assert.Null(config["meSecret1"]); + Assert.Equal("SecretValue2", config["Secret2"]); + } + + [Fact] + public void CanUnIgnoreDefaultFiles() + { + var testFileProvider = new TestFileProvider( + new TestFile("ignore.Secret0", "SecretValue0"), + new TestFile("ignore.Secret1", "SecretValue1"), + new TestFile("Secret2", "SecretValue2")); + + var config = new ConfigurationBuilder() + .AddKeyPerFile(o => + { + o.FileProvider = testFileProvider; + o.IgnorePrefix = null; + }) + .Build(); + + Assert.Equal("SecretValue0", config["ignore.Secret0"]); + Assert.Equal("SecretValue1", config["ignore.Secret1"]); + Assert.Equal("SecretValue2", config["Secret2"]); + } + } + + class TestFileProvider : IFileProvider + { + IDirectoryContents _contents; + + public TestFileProvider(params IFileInfo[] files) + { + _contents = new TestDirectoryContents(files); + } + + public IDirectoryContents GetDirectoryContents(string subpath) + { + return _contents; + } + + public IFileInfo GetFileInfo(string subpath) + { + throw new NotImplementedException(); + } + + public IChangeToken Watch(string filter) + { + throw new NotImplementedException(); + } + } + + class TestDirectoryContents : IDirectoryContents + { + List _list; + + public TestDirectoryContents(params IFileInfo[] files) + { + _list = new List(files); + } + + public bool Exists + { + get + { + return true; + } + } + + public IEnumerator GetEnumerator() + { + return _list.GetEnumerator(); + } + + IEnumerator IEnumerable.GetEnumerator() + { + return GetEnumerator(); + } + } + + //TODO: Probably need a directory and file type. + class TestFile : IFileInfo + { + private string _name; + private string _contents; + + public bool Exists + { + get + { + return true; + } + } + + public bool IsDirectory + { + get; + } + + public DateTimeOffset LastModified + { + get + { + throw new NotImplementedException(); + } + } + + public long Length + { + get + { + throw new NotImplementedException(); + } + } + + public string Name + { + get + { + return _name; + } + } + + public string PhysicalPath + { + get + { + throw new NotImplementedException(); + } + } + + public TestFile(string name) + { + _name = name; + IsDirectory = true; + } + + public TestFile(string name, string contents) + { + _name = name; + _contents = contents; + } + + public Stream CreateReadStream() + { + if(IsDirectory) + { + throw new InvalidOperationException("Cannot create stream from directory"); + } + + return new MemoryStream(Encoding.UTF8.GetBytes(_contents)); + } + } +} \ No newline at end of file diff --git a/src/Configuration.KeyPerFile/test/Microsoft.Extensions.Configuration.KeyPerFile.Tests.csproj b/src/Configuration.KeyPerFile/test/Microsoft.Extensions.Configuration.KeyPerFile.Tests.csproj new file mode 100644 index 0000000000..634056a345 --- /dev/null +++ b/src/Configuration.KeyPerFile/test/Microsoft.Extensions.Configuration.KeyPerFile.Tests.csproj @@ -0,0 +1,11 @@ + + + + $(StandardTestTfms) + + + + + + + From ac89e3e9bfc5eec640c9a2fd68416a443ac667c3 Mon Sep 17 00:00:00 2001 From: Nate McMaster Date: Mon, 5 Nov 2018 16:20:37 -0800 Subject: [PATCH 0011/1101] Merge branch 'release/2.1' into release/2.2 \n\nCommit migrated from https://github.com/dotnet/extensions/commit/d94eb17013c924e4b932b89bbbe729fa2bf1899e --- src/Configuration.KeyPerFile/test/KeyPerFileTests.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Configuration.KeyPerFile/test/KeyPerFileTests.cs b/src/Configuration.KeyPerFile/test/KeyPerFileTests.cs index d55387a404..d409c0eab0 100644 --- a/src/Configuration.KeyPerFile/test/KeyPerFileTests.cs +++ b/src/Configuration.KeyPerFile/test/KeyPerFileTests.cs @@ -28,7 +28,7 @@ namespace Microsoft.Extensions.Configuration.KeyPerFile.Test public void ThrowsWhenNotOptionalAndDirectoryDoesntExist() { var e = Assert.Throws(() => new ConfigurationBuilder().AddKeyPerFile("nonexistent", false).Build()); - Assert.Contains("The directory name", e.Message); + Assert.Contains("The path must be absolute.", e.Message); } [Fact] @@ -305,4 +305,4 @@ namespace Microsoft.Extensions.Configuration.KeyPerFile.Test return new MemoryStream(Encoding.UTF8.GetBytes(_contents)); } } -} \ No newline at end of file +} From a8c8ddbb451f81cf88024f1925067df2ddc365ff Mon Sep 17 00:00:00 2001 From: Nate McMaster Date: Tue, 6 Nov 2018 13:11:45 -0800 Subject: [PATCH 0012/1101] Reorganize source code in preparation to move into aspnet/Extensions Prior to reorganization, this source code was found in https://github.com/aspnet/Logging/tree/8270c545224e8734d7297e54edef5c584ee82f01 --- src/Testing/src/AssemblyTestLog.cs | 305 ++++++++++++++++++ ...Microsoft.Extensions.Logging.Testing.props | 8 + src/Testing/test/AssemblyTestLogTests.cs | 207 ++++++++++++ src/Testing/test/LoggedTestXunitTests.cs | 142 ++++++++ src/Testing/test/TestTestOutputHelper.cs | 36 +++ 5 files changed, 698 insertions(+) create mode 100644 src/Testing/src/AssemblyTestLog.cs create mode 100644 src/Testing/src/build/Microsoft.Extensions.Logging.Testing.props create mode 100644 src/Testing/test/AssemblyTestLogTests.cs create mode 100644 src/Testing/test/LoggedTestXunitTests.cs create mode 100644 src/Testing/test/TestTestOutputHelper.cs diff --git a/src/Testing/src/AssemblyTestLog.cs b/src/Testing/src/AssemblyTestLog.cs new file mode 100644 index 0000000000..97a67b11fa --- /dev/null +++ b/src/Testing/src/AssemblyTestLog.cs @@ -0,0 +1,305 @@ +// 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.Collections.Generic; +using System.Diagnostics; +using System.IO; +using System.Linq; +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; +using System.Text; +using Microsoft.Extensions.DependencyInjection; +using Serilog; +using Serilog.Extensions.Logging; +using Xunit.Abstractions; + +namespace Microsoft.Extensions.Logging.Testing +{ + public class AssemblyTestLog : IDisposable + { + public static readonly string OutputDirectoryEnvironmentVariableName = "ASPNETCORE_TEST_LOG_DIR"; + private static readonly string MaxPathLengthEnvironmentVariableName = "ASPNETCORE_TEST_LOG_MAXPATH"; + private static readonly string LogFileExtension = ".log"; + private static readonly int MaxPathLength = GetMaxPathLength(); + private static char[] InvalidFileChars = new char[] + { + '\"', '<', '>', '|', '\0', + (char)1, (char)2, (char)3, (char)4, (char)5, (char)6, (char)7, (char)8, (char)9, (char)10, + (char)11, (char)12, (char)13, (char)14, (char)15, (char)16, (char)17, (char)18, (char)19, (char)20, + (char)21, (char)22, (char)23, (char)24, (char)25, (char)26, (char)27, (char)28, (char)29, (char)30, + (char)31, ':', '*', '?', '\\', '/', ' ', (char)127 + }; + + private static readonly object _lock = new object(); + private static readonly Dictionary _logs = new Dictionary(); + + private readonly ILoggerFactory _globalLoggerFactory; + private readonly ILogger _globalLogger; + private readonly string _baseDirectory; + private readonly string _assemblyName; + private readonly IServiceProvider _serviceProvider; + + private static int GetMaxPathLength() + { + var maxPathString = Environment.GetEnvironmentVariable(MaxPathLengthEnvironmentVariableName); + var defaultMaxPath = 245; + return string.IsNullOrEmpty(maxPathString) ? defaultMaxPath : int.Parse(maxPathString); + } + + private AssemblyTestLog(ILoggerFactory globalLoggerFactory, ILogger globalLogger, string baseDirectory, string assemblyName, IServiceProvider serviceProvider) + { + _globalLoggerFactory = globalLoggerFactory; + _globalLogger = globalLogger; + _baseDirectory = baseDirectory; + _assemblyName = assemblyName; + _serviceProvider = serviceProvider; + } + + public IDisposable StartTestLog(ITestOutputHelper output, string className, out ILoggerFactory loggerFactory, [CallerMemberName] string testName = null) => + StartTestLog(output, className, out loggerFactory, LogLevel.Debug, testName); + + public IDisposable StartTestLog(ITestOutputHelper output, string className, out ILoggerFactory loggerFactory, LogLevel minLogLevel, [CallerMemberName] string testName = null) => + StartTestLog(output, className, out loggerFactory, minLogLevel, out var _, testName); + + internal IDisposable StartTestLog(ITestOutputHelper output, string className, out ILoggerFactory loggerFactory, LogLevel minLogLevel, out string resolvedTestName, [CallerMemberName] string testName = null) + { + var serviceProvider = CreateLoggerServices(output, className, minLogLevel, out resolvedTestName, testName); + var factory = serviceProvider.GetRequiredService(); + loggerFactory = factory; + var logger = loggerFactory.CreateLogger("TestLifetime"); + + var stopwatch = Stopwatch.StartNew(); + + var scope = logger.BeginScope("Test: {testName}", testName); + + _globalLogger.LogInformation("Starting test {testName}", testName); + logger.LogInformation("Starting test {testName}", testName); + + return new Disposable(() => + { + stopwatch.Stop(); + _globalLogger.LogInformation("Finished test {testName} in {duration}s", testName, stopwatch.Elapsed.TotalSeconds); + logger.LogInformation("Finished test {testName} in {duration}s", testName, stopwatch.Elapsed.TotalSeconds); + scope.Dispose(); + factory.Dispose(); + (serviceProvider as IDisposable)?.Dispose(); + }); + } + + public ILoggerFactory CreateLoggerFactory(ITestOutputHelper output, string className, [CallerMemberName] string testName = null) => + CreateLoggerFactory(output, className, LogLevel.Trace, testName); + + public ILoggerFactory CreateLoggerFactory(ITestOutputHelper output, string className, LogLevel minLogLevel, [CallerMemberName] string testName = null) + { + return CreateLoggerServices(output, className, minLogLevel, out var _, testName).GetRequiredService(); + } + + public IServiceProvider CreateLoggerServices(ITestOutputHelper output, string className, LogLevel minLogLevel, out string normalizedTestName, [CallerMemberName] string testName = null) + { + normalizedTestName = string.Empty; + + // Try to shorten the class name using the assembly name + if (className.StartsWith(_assemblyName + ".")) + { + className = className.Substring(_assemblyName.Length + 1); + } + + SerilogLoggerProvider serilogLoggerProvider = null; + if (!string.IsNullOrEmpty(_baseDirectory)) + { + var testOutputDirectory = Path.Combine(GetAssemblyBaseDirectory(_assemblyName, _baseDirectory), className); + testName = RemoveIllegalFileChars(testName); + + if (testOutputDirectory.Length + testName.Length + LogFileExtension.Length >= MaxPathLength) + { + _globalLogger.LogWarning($"Test name {testName} is too long. Please shorten test name."); + + // Shorten the test name by removing the middle portion of the testname + var testNameLength = MaxPathLength - testOutputDirectory.Length - LogFileExtension.Length; + + if (testNameLength <= 0) + { + throw new InvalidOperationException("Output file path could not be constructed due to max path length restrictions. Please shorten test assembly, class or method names."); + } + + testName = testName.Substring(0, testNameLength / 2) + testName.Substring(testName.Length - testNameLength / 2, testNameLength / 2); + + _globalLogger.LogWarning($"To prevent long paths test name was shortened to {testName}."); + } + + var testOutputFile = Path.Combine(testOutputDirectory, $"{testName}{LogFileExtension}"); + + if (File.Exists(testOutputFile)) + { + _globalLogger.LogWarning($"Output log file {testOutputFile} already exists. Please try to keep log file names unique."); + + for (var i = 0; i < 1000; i++) + { + testOutputFile = Path.Combine(testOutputDirectory, $"{testName}.{i}{LogFileExtension}"); + + if (!File.Exists(testOutputFile)) + { + _globalLogger.LogWarning($"To resolve log file collision, the enumerated file {testOutputFile} will be used."); + testName = $"{testName}.{i}"; + break; + } + } + } + + normalizedTestName = testName; + serilogLoggerProvider = ConfigureFileLogging(testOutputFile); + } + + var serviceCollection = new ServiceCollection(); + serviceCollection.AddLogging(builder => + { + builder.SetMinimumLevel(minLogLevel); + + if (output != null) + { + builder.AddXunit(output, minLogLevel); + } + + if (serilogLoggerProvider != null) + { + // Use a factory so that the container will dispose it + builder.Services.AddSingleton(_ => serilogLoggerProvider); + } + }); + + return serviceCollection.BuildServiceProvider(); + } + + public static AssemblyTestLog Create(string assemblyName, string baseDirectory) + { + SerilogLoggerProvider serilogLoggerProvider = null; + var globalLogDirectory = GetAssemblyBaseDirectory(assemblyName, baseDirectory); + if (!string.IsNullOrEmpty(globalLogDirectory)) + { + var globalLogFileName = Path.Combine(globalLogDirectory, "global.log"); + serilogLoggerProvider = ConfigureFileLogging(globalLogFileName); + } + + var serviceCollection = new ServiceCollection(); + + serviceCollection.AddLogging(builder => + { + // Global logging, when it's written, is expected to be outputted. So set the log level to minimum. + builder.SetMinimumLevel(LogLevel.Trace); + + if (serilogLoggerProvider != null) + { + // Use a factory so that the container will dispose it + builder.Services.AddSingleton(_ => serilogLoggerProvider); + } + }); + + var serviceProvider = serviceCollection.BuildServiceProvider(); + var loggerFactory = serviceProvider.GetRequiredService(); + + var logger = loggerFactory.CreateLogger("GlobalTestLog"); + logger.LogInformation($"Global Test Logging initialized. Set the '{OutputDirectoryEnvironmentVariableName}' Environment Variable in order to create log files on disk."); + return new AssemblyTestLog(loggerFactory, logger, baseDirectory, assemblyName, serviceProvider); + } + + public static AssemblyTestLog ForAssembly(Assembly assembly) + { + lock (_lock) + { + if (!_logs.TryGetValue(assembly, out var log)) + { + var assemblyName = assembly.GetName().Name; + var baseDirectory = Environment.GetEnvironmentVariable(OutputDirectoryEnvironmentVariableName); + log = Create(assemblyName, baseDirectory); + _logs[assembly] = log; + + // Try to clear previous logs + var assemblyBaseDirectory = GetAssemblyBaseDirectory(assemblyName, baseDirectory); + if (Directory.Exists(assemblyBaseDirectory)) + { + try + { + Directory.Delete(assemblyBaseDirectory, recursive: true); + } + catch {} + } + } + return log; + } + } + + private static string GetAssemblyBaseDirectory(string assemblyName, string baseDirectory) + { + if (!string.IsNullOrEmpty(baseDirectory)) + { + return Path.Combine(baseDirectory, assemblyName, RuntimeInformation.FrameworkDescription.TrimStart('.')); + } + return string.Empty; + } + + private static SerilogLoggerProvider ConfigureFileLogging(string fileName) + { + var dir = Path.GetDirectoryName(fileName); + if (!Directory.Exists(dir)) + { + Directory.CreateDirectory(dir); + } + + if (File.Exists(fileName)) + { + File.Delete(fileName); + } + + var serilogger = new LoggerConfiguration() + .Enrich.FromLogContext() + .MinimumLevel.Verbose() + .WriteTo.File(fileName, outputTemplate: "{Timestamp:yyyy-MM-dd HH:mm:ss.fff zzz} [{SourceContext}] [{Level}] {Message}{NewLine}{Exception}", flushToDiskInterval: TimeSpan.FromSeconds(1), shared: true) + .CreateLogger(); + return new SerilogLoggerProvider(serilogger, dispose: true); + } + + private static string RemoveIllegalFileChars(string s) + { + var sb = new StringBuilder(); + + foreach (var c in s) + { + if (InvalidFileChars.Contains(c)) + { + if (sb.Length > 0 && sb[sb.Length - 1] != '_') + { + sb.Append('_'); + } + } + else + { + sb.Append(c); + } + } + return sb.ToString(); + } + + public void Dispose() + { + (_serviceProvider as IDisposable)?.Dispose(); + _globalLoggerFactory.Dispose(); + } + + private class Disposable : IDisposable + { + private Action _action; + + public Disposable(Action action) + { + _action = action; + } + + public void Dispose() + { + _action(); + } + } + } +} diff --git a/src/Testing/src/build/Microsoft.Extensions.Logging.Testing.props b/src/Testing/src/build/Microsoft.Extensions.Logging.Testing.props new file mode 100644 index 0000000000..f98e3e13b5 --- /dev/null +++ b/src/Testing/src/build/Microsoft.Extensions.Logging.Testing.props @@ -0,0 +1,8 @@ + + + + <_Parameter1>Microsoft.Extensions.Logging.Testing.LoggedTestFramework + <_Parameter2>Microsoft.Extensions.Logging.Testing + + + \ No newline at end of file diff --git a/src/Testing/test/AssemblyTestLogTests.cs b/src/Testing/test/AssemblyTestLogTests.cs new file mode 100644 index 0000000000..0efadb4367 --- /dev/null +++ b/src/Testing/test/AssemblyTestLogTests.cs @@ -0,0 +1,207 @@ +// 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.IO; +using System.Linq; +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; +using System.Text.RegularExpressions; +using System.Threading.Tasks; +using Xunit; + +namespace Microsoft.Extensions.Logging.Testing.Tests +{ + public class AssemblyTestLogTests : LoggedTest + { + private static readonly Assembly ThisAssembly = typeof(AssemblyTestLog).GetTypeInfo().Assembly; + + [Fact] + public void FullClassNameUsedWhenShortClassNameAttributeNotSpecified() + { + Assert.Equal(GetType().FullName, ResolvedTestClassName); + } + + [Fact] + public void ForAssembly_ReturnsSameInstanceForSameAssembly() + { + Assert.Same( + AssemblyTestLog.ForAssembly(ThisAssembly), + AssemblyTestLog.ForAssembly(ThisAssembly)); + } + + [Fact] + public void TestLogWritesToITestOutputHelper() + { + var output = new TestTestOutputHelper(); + var assemblyLog = AssemblyTestLog.Create("NonExistant.Test.Assembly", baseDirectory: null); + + using (assemblyLog.StartTestLog(output, "NonExistant.Test.Class", out var loggerFactory)) + { + var logger = loggerFactory.CreateLogger("TestLogger"); + logger.LogInformation("Information!"); + + // Trace is disabled by default + logger.LogTrace("Trace!"); + } + + Assert.Equal(@"[TIMESTAMP] TestLifetime Information: Starting test TestLogWritesToITestOutputHelper +[TIMESTAMP] TestLogger Information: Information! +[TIMESTAMP] TestLifetime Information: Finished test TestLogWritesToITestOutputHelper in DURATION +", MakeConsistent(output.Output), ignoreLineEndingDifferences: true); + } + + [Fact] + private Task TestLogEscapesIllegalFileNames() => + RunTestLogFunctionalTest((tempDir) => + { + var illegalTestName = "Testing-https://localhost:5000"; + var escapedTestName = "Testing-https_localhost_5000"; + using (var testAssemblyLog = AssemblyTestLog.Create("FakeTestAssembly", baseDirectory: tempDir)) + using (testAssemblyLog.StartTestLog(output: null, className: "FakeTestAssembly.FakeTestClass", loggerFactory: out var testLoggerFactory, minLogLevel: LogLevel.Trace, resolvedTestName: out var resolvedTestname, testName: illegalTestName)) + { + Assert.Equal(escapedTestName, resolvedTestname); + } + }); + + [Fact] + public Task TestLogWritesToGlobalLogFile() => + RunTestLogFunctionalTest((tempDir) => + { + // Because this test writes to a file, it is a functional test and should be logged + // but it's also testing the test logging facility. So this is pretty meta ;) + var logger = LoggerFactory.CreateLogger("Test"); + + using (var testAssemblyLog = AssemblyTestLog.Create("FakeTestAssembly", tempDir)) + { + logger.LogInformation("Created test log in {baseDirectory}", tempDir); + + using (testAssemblyLog.StartTestLog(output: null, className: "FakeTestAssembly.FakeTestClass", loggerFactory: out var testLoggerFactory, minLogLevel: LogLevel.Trace, testName: "FakeTestName")) + { + var testLogger = testLoggerFactory.CreateLogger("TestLogger"); + testLogger.LogInformation("Information!"); + testLogger.LogTrace("Trace!"); + } + } + + logger.LogInformation("Finished test log in {baseDirectory}", tempDir); + + var globalLogPath = Path.Combine(tempDir, "FakeTestAssembly", RuntimeInformation.FrameworkDescription.TrimStart('.'), "global.log"); + var testLog = Path.Combine(tempDir, "FakeTestAssembly", RuntimeInformation.FrameworkDescription.TrimStart('.'), "FakeTestClass", $"FakeTestName.log"); + + Assert.True(File.Exists(globalLogPath), $"Expected global log file {globalLogPath} to exist"); + Assert.True(File.Exists(testLog), $"Expected test log file {testLog} to exist"); + + var globalLogContent = MakeConsistent(File.ReadAllText(globalLogPath)); + var testLogContent = MakeConsistent(File.ReadAllText(testLog)); + + Assert.Equal(@"[GlobalTestLog] [Information] Global Test Logging initialized. Set the 'ASPNETCORE_TEST_LOG_DIR' Environment Variable in order to create log files on disk. +[GlobalTestLog] [Information] Starting test ""FakeTestName"" +[GlobalTestLog] [Information] Finished test ""FakeTestName"" in DURATION +", globalLogContent, ignoreLineEndingDifferences: true); + Assert.Equal(@"[TestLifetime] [Information] Starting test ""FakeTestName"" +[TestLogger] [Information] Information! +[TestLogger] [Verbose] Trace! +[TestLifetime] [Information] Finished test ""FakeTestName"" in DURATION +", testLogContent, ignoreLineEndingDifferences: true); + }); + + [Fact] + public Task TestLogTruncatesTestNameToAvoidLongPaths() => + RunTestLogFunctionalTest((tempDir) => + { + var longTestName = new string('0', 50) + new string('1', 50) + new string('2', 50) + new string('3', 50) + new string('4', 50); + var logger = LoggerFactory.CreateLogger("Test"); + using (var testAssemblyLog = AssemblyTestLog.Create("FakeTestAssembly", tempDir)) + { + logger.LogInformation("Created test log in {baseDirectory}", tempDir); + + using (testAssemblyLog.StartTestLog(output: null, className: "FakeTestAssembly.FakeTestClass", loggerFactory: out var testLoggerFactory, minLogLevel: LogLevel.Trace, testName: longTestName)) + { + testLoggerFactory.CreateLogger("TestLogger").LogInformation("Information!"); + } + } + logger.LogInformation("Finished test log in {baseDirectory}", tempDir); + + var testLogFiles = new DirectoryInfo(Path.Combine(tempDir, "FakeTestAssembly", RuntimeInformation.FrameworkDescription.TrimStart('.'), "FakeTestClass")).EnumerateFiles(); + var testLog = Assert.Single(testLogFiles); + var testFileName = Path.GetFileNameWithoutExtension(testLog.Name); + + // The first half of the file comes from the beginning of the test name passed to the logger + Assert.Equal(longTestName.Substring(0, testFileName.Length / 2), testFileName.Substring(0, testFileName.Length / 2)); + // The last half of the file comes from the ending of the test name passed to the logger + Assert.Equal(longTestName.Substring(longTestName.Length - testFileName.Length / 2, testFileName.Length / 2), testFileName.Substring(testFileName.Length - testFileName.Length / 2, testFileName.Length / 2)); + }); + + [Fact] + public Task TestLogEnumerateFilenamesToAvoidCollisions() => + RunTestLogFunctionalTest((tempDir) => + { + var logger = LoggerFactory.CreateLogger("Test"); + using (var testAssemblyLog = AssemblyTestLog.Create("FakeTestAssembly", tempDir)) + { + logger.LogInformation("Created test log in {baseDirectory}", tempDir); + + for (var i = 0; i < 10; i++) + { + using (testAssemblyLog.StartTestLog(output: null, className: "FakeTestAssembly.FakeTestClass", loggerFactory: out var testLoggerFactory, minLogLevel: LogLevel.Trace, testName: "FakeTestName")) + { + testLoggerFactory.CreateLogger("TestLogger").LogInformation("Information!"); + } + } + } + logger.LogInformation("Finished test log in {baseDirectory}", tempDir); + + // The first log file exists + Assert.True(File.Exists(Path.Combine(tempDir, "FakeTestAssembly", RuntimeInformation.FrameworkDescription.TrimStart('.'), "FakeTestClass", $"FakeTestName.log"))); + + // Subsequent files exist + for (var i = 0; i < 9; i++) + { + Assert.True(File.Exists(Path.Combine(tempDir, "FakeTestAssembly", RuntimeInformation.FrameworkDescription.TrimStart('.'), "FakeTestClass", $"FakeTestName.{i}.log"))); + } + }); + + private static readonly Regex TimestampRegex = new Regex(@"\d+-\d+-\d+T\d+:\d+:\d+"); + private static readonly Regex DurationRegex = new Regex(@"[^ ]+s$"); + + private async Task RunTestLogFunctionalTest(Action action, [CallerMemberName] string testName = null) + { + var tempDir = Path.Combine(Path.GetTempPath(), $"TestLogging_{Guid.NewGuid().ToString("N")}"); + try + { + action(tempDir); + } + finally + { + if (Directory.Exists(tempDir)) + { + try + { + Directory.Delete(tempDir, recursive: true); + } + catch + { + await Task.Delay(100); + Directory.Delete(tempDir, recursive: true); + } + } + } + } + + private static string MakeConsistent(string input) + { + return string.Join(Environment.NewLine, input.Split(new[] { Environment.NewLine }, StringSplitOptions.None) + .Select(line => + { + var strippedPrefix = line.IndexOf("[") >= 0 ? line.Substring(line.IndexOf("[")) : line; + + var strippedDuration = + DurationRegex.Replace(strippedPrefix, "DURATION"); + var strippedTimestamp = TimestampRegex.Replace(strippedDuration, "TIMESTAMP"); + return strippedTimestamp; + })); + } + } +} diff --git a/src/Testing/test/LoggedTestXunitTests.cs b/src/Testing/test/LoggedTestXunitTests.cs new file mode 100644 index 0000000000..31fd6d631f --- /dev/null +++ b/src/Testing/test/LoggedTestXunitTests.cs @@ -0,0 +1,142 @@ +// 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 Microsoft.AspNetCore.Testing.xunit; +using Microsoft.Extensions.DependencyInjection; +using Xunit; +using Xunit.Abstractions; + +namespace Microsoft.Extensions.Logging.Testing.Tests +{ + [ShortClassName] + public class LoggedTestXunitTests : TestLoggedTest + { + private readonly ITestOutputHelper _output; + + public LoggedTestXunitTests(ITestOutputHelper output) + { + _output = output; + } + + [Fact] + public void ShortClassNameUsedWhenShortClassNameAttributeSpecified() + { + Assert.Equal(GetType().Name, ResolvedTestClassName); + } + + [Fact] + public void LoggedTestTestOutputHelperSameInstanceAsInjectedConstructorArg() + { + Assert.Same(_output, TestOutputHelper); + } + + [Fact] + public void LoggedFactInitializesLoggedTestProperties() + { + Assert.NotNull(Logger); + Assert.NotNull(LoggerFactory); + Assert.NotNull(TestSink); + Assert.NotNull(TestOutputHelper); + } + + [Theory] + [InlineData("Hello world")] + public void LoggedTheoryInitializesLoggedTestProperties(string argument) + { + Assert.NotNull(Logger); + Assert.NotNull(LoggerFactory); + Assert.NotNull(TestSink); + Assert.NotNull(TestOutputHelper); + // Use the test argument + Assert.NotNull(argument); + } + + [ConditionalFact] + public void ConditionalLoggedFactGetsInitializedLoggerFactory() + { + Assert.NotNull(Logger); + Assert.NotNull(LoggerFactory); + Assert.NotNull(TestSink); + Assert.NotNull(TestOutputHelper); + } + + [ConditionalTheory] + [InlineData("Hello world")] + public void LoggedConditionalTheoryInitializesLoggedTestProperties(string argument) + { + Assert.NotNull(Logger); + Assert.NotNull(LoggerFactory); + Assert.NotNull(TestSink); + Assert.NotNull(TestOutputHelper); + // Use the test argument + Assert.NotNull(argument); + } + + [Fact] + [LogLevel(LogLevel.Information)] + public void LoggedFactFilteredByLogLevel() + { + Logger.LogInformation("Information"); + Logger.LogDebug("Debug"); + + var message = Assert.Single(TestSink.Writes); + Assert.Equal(LogLevel.Information, message.LogLevel); + Assert.Equal("Information", message.Formatter(message.State, null)); + } + + [Theory] + [InlineData("Hello world")] + [LogLevel(LogLevel.Information)] + public void LoggedTheoryFilteredByLogLevel(string argument) + { + Logger.LogInformation("Information"); + Logger.LogDebug("Debug"); + + var message = Assert.Single(TestSink.Writes); + Assert.Equal(LogLevel.Information, message.LogLevel); + Assert.Equal("Information", message.Formatter(message.State, null)); + + // Use the test argument + Assert.NotNull(argument); + } + + [Fact] + public void AddTestLoggingUpdatedWhenLoggerFactoryIsSet() + { + var loggerFactory = new LoggerFactory(); + var serviceCollection = new ServiceCollection(); + + LoggerFactory = loggerFactory; + AddTestLogging(serviceCollection); + + Assert.Same(loggerFactory, serviceCollection.BuildServiceProvider().GetRequiredService()); + } + + [ConditionalTheory] + [EnvironmentVariableSkipCondition("ASPNETCORE_TEST_LOG_DIR", "")] // The test name is only generated when logging is enabled via the environment variable + [InlineData(null)] + public void LoggedTheoryNullArgumentsAreEscaped(string argument) + { + Assert.NotNull(LoggerFactory); + Assert.Equal($"{nameof(LoggedTheoryNullArgumentsAreEscaped)}_null", ResolvedTestMethodName); + // Use the test argument + Assert.Null(argument); + } + + [Fact] + public void AdditionalSetupInvoked() + { + Assert.True(SetupInvoked); + } + } + + public class TestLoggedTest : LoggedTest + { + public bool SetupInvoked { get; private set; } = false; + + public override void AdditionalSetup() + { + SetupInvoked = true; + } + } +} diff --git a/src/Testing/test/TestTestOutputHelper.cs b/src/Testing/test/TestTestOutputHelper.cs new file mode 100644 index 0000000000..7043fe4ed2 --- /dev/null +++ b/src/Testing/test/TestTestOutputHelper.cs @@ -0,0 +1,36 @@ +// 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.Text; +using Xunit.Abstractions; + +namespace Microsoft.Extensions.Logging.Testing.Tests +{ + public class TestTestOutputHelper : ITestOutputHelper + { + private StringBuilder _output = new StringBuilder(); + + public bool Throw { get; set; } + + public string Output => _output.ToString(); + + public void WriteLine(string message) + { + if (Throw) + { + throw new Exception("Boom!"); + } + _output.AppendLine(message); + } + + public void WriteLine(string format, params object[] args) + { + if (Throw) + { + throw new Exception("Boom!"); + } + _output.AppendLine(string.Format(format, args)); + } + } +} From fd100ade9e885b4a8adced9414434cde0bf35b7a Mon Sep 17 00:00:00 2001 From: Nate McMaster Date: Tue, 6 Nov 2018 15:28:23 -0800 Subject: [PATCH 0013/1101] Reorganize source code in preparation to move into aspnet/Extensions Prior to reorganization, this source code was found in https://github.com/aspnet/Logging/tree/5381f42ded1f41a3b0960bba799aed53da411401 --- src/Testing/src/AssemblyTestLog.cs | 119 +++++++++++------- src/Testing/src/LoggedTest/ILoggedTest.cs | 23 ++++ src/Testing/src/LoggedTest/LoggedTest.cs | 24 ++++ src/Testing/src/LoggedTest/LoggedTestBase.cs | 82 ++++++++++++ .../src/TestFrameworkFileLoggerAttribute.cs | 20 +++ ...Microsoft.Extensions.Logging.Testing.props | 27 +++- src/Testing/test/AssemblyTestLogTests.cs | 71 ++++++----- src/Testing/test/LoggedTestXunitTests.cs | 20 ++- 8 files changed, 304 insertions(+), 82 deletions(-) create mode 100644 src/Testing/src/LoggedTest/ILoggedTest.cs create mode 100644 src/Testing/src/LoggedTest/LoggedTest.cs create mode 100644 src/Testing/src/LoggedTest/LoggedTestBase.cs create mode 100644 src/Testing/src/TestFrameworkFileLoggerAttribute.cs diff --git a/src/Testing/src/AssemblyTestLog.cs b/src/Testing/src/AssemblyTestLog.cs index 97a67b11fa..e84df52554 100644 --- a/src/Testing/src/AssemblyTestLog.cs +++ b/src/Testing/src/AssemblyTestLog.cs @@ -8,10 +8,11 @@ using System.IO; using System.Linq; using System.Reflection; using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; using System.Text; using Microsoft.Extensions.DependencyInjection; using Serilog; +using Serilog.Core; +using Serilog.Events; using Serilog.Extensions.Logging; using Xunit.Abstractions; @@ -19,7 +20,6 @@ namespace Microsoft.Extensions.Logging.Testing { public class AssemblyTestLog : IDisposable { - public static readonly string OutputDirectoryEnvironmentVariableName = "ASPNETCORE_TEST_LOG_DIR"; private static readonly string MaxPathLengthEnvironmentVariableName = "ASPNETCORE_TEST_LOG_MAXPATH"; private static readonly string LogFileExtension = ".log"; private static readonly int MaxPathLength = GetMaxPathLength(); @@ -38,7 +38,7 @@ namespace Microsoft.Extensions.Logging.Testing private readonly ILoggerFactory _globalLoggerFactory; private readonly ILogger _globalLogger; private readonly string _baseDirectory; - private readonly string _assemblyName; + private readonly Assembly _assembly; private readonly IServiceProvider _serviceProvider; private static int GetMaxPathLength() @@ -48,12 +48,12 @@ namespace Microsoft.Extensions.Logging.Testing return string.IsNullOrEmpty(maxPathString) ? defaultMaxPath : int.Parse(maxPathString); } - private AssemblyTestLog(ILoggerFactory globalLoggerFactory, ILogger globalLogger, string baseDirectory, string assemblyName, IServiceProvider serviceProvider) + private AssemblyTestLog(ILoggerFactory globalLoggerFactory, ILogger globalLogger, string baseDirectory, Assembly assembly, IServiceProvider serviceProvider) { _globalLoggerFactory = globalLoggerFactory; _globalLogger = globalLogger; _baseDirectory = baseDirectory; - _assemblyName = assemblyName; + _assembly = assembly; _serviceProvider = serviceProvider; } @@ -61,11 +61,12 @@ namespace Microsoft.Extensions.Logging.Testing StartTestLog(output, className, out loggerFactory, LogLevel.Debug, testName); public IDisposable StartTestLog(ITestOutputHelper output, string className, out ILoggerFactory loggerFactory, LogLevel minLogLevel, [CallerMemberName] string testName = null) => - StartTestLog(output, className, out loggerFactory, minLogLevel, out var _, testName); + StartTestLog(output, className, out loggerFactory, minLogLevel, out var _, out var _, testName); - internal IDisposable StartTestLog(ITestOutputHelper output, string className, out ILoggerFactory loggerFactory, LogLevel minLogLevel, out string resolvedTestName, [CallerMemberName] string testName = null) + internal IDisposable StartTestLog(ITestOutputHelper output, string className, out ILoggerFactory loggerFactory, LogLevel minLogLevel, out string resolvedTestName, out string logOutputDirectory, [CallerMemberName] string testName = null) { - var serviceProvider = CreateLoggerServices(output, className, minLogLevel, out resolvedTestName, testName); + var logStart = DateTimeOffset.UtcNow; + var serviceProvider = CreateLoggerServices(output, className, minLogLevel, out resolvedTestName, out logOutputDirectory, testName, logStart); var factory = serviceProvider.GetRequiredService(); loggerFactory = factory; var logger = loggerFactory.CreateLogger("TestLifetime"); @@ -75,7 +76,7 @@ namespace Microsoft.Extensions.Logging.Testing var scope = logger.BeginScope("Test: {testName}", testName); _globalLogger.LogInformation("Starting test {testName}", testName); - logger.LogInformation("Starting test {testName}", testName); + logger.LogInformation("Starting test {testName} at {logStart}", testName, logStart.ToString("s")); return new Disposable(() => { @@ -88,36 +89,39 @@ namespace Microsoft.Extensions.Logging.Testing }); } - public ILoggerFactory CreateLoggerFactory(ITestOutputHelper output, string className, [CallerMemberName] string testName = null) => - CreateLoggerFactory(output, className, LogLevel.Trace, testName); + public ILoggerFactory CreateLoggerFactory(ITestOutputHelper output, string className, [CallerMemberName] string testName = null, DateTimeOffset? logStart = null) + => CreateLoggerFactory(output, className, LogLevel.Trace, testName, logStart); - public ILoggerFactory CreateLoggerFactory(ITestOutputHelper output, string className, LogLevel minLogLevel, [CallerMemberName] string testName = null) - { - return CreateLoggerServices(output, className, minLogLevel, out var _, testName).GetRequiredService(); - } + public ILoggerFactory CreateLoggerFactory(ITestOutputHelper output, string className, LogLevel minLogLevel, [CallerMemberName] string testName = null, DateTimeOffset? logStart = null) + => CreateLoggerServices(output, className, minLogLevel, out var _, out var _, testName, logStart).GetRequiredService(); - public IServiceProvider CreateLoggerServices(ITestOutputHelper output, string className, LogLevel minLogLevel, out string normalizedTestName, [CallerMemberName] string testName = null) + public IServiceProvider CreateLoggerServices(ITestOutputHelper output, string className, LogLevel minLogLevel, out string normalizedTestName, [CallerMemberName] string testName = null, DateTimeOffset? logStart = null) + => CreateLoggerServices(output, className, minLogLevel, out normalizedTestName, out var _, testName, logStart); + + public IServiceProvider CreateLoggerServices(ITestOutputHelper output, string className, LogLevel minLogLevel, out string normalizedTestName, out string logOutputDirectory, [CallerMemberName] string testName = null, DateTimeOffset? logStart = null) { normalizedTestName = string.Empty; + logOutputDirectory = string.Empty; + var assemblyName = _assembly.GetName().Name; // Try to shorten the class name using the assembly name - if (className.StartsWith(_assemblyName + ".")) + if (className.StartsWith(assemblyName + ".")) { - className = className.Substring(_assemblyName.Length + 1); + className = className.Substring(assemblyName.Length + 1); } SerilogLoggerProvider serilogLoggerProvider = null; if (!string.IsNullOrEmpty(_baseDirectory)) { - var testOutputDirectory = Path.Combine(GetAssemblyBaseDirectory(_assemblyName, _baseDirectory), className); + logOutputDirectory = Path.Combine(GetAssemblyBaseDirectory(_baseDirectory, _assembly), className); testName = RemoveIllegalFileChars(testName); - if (testOutputDirectory.Length + testName.Length + LogFileExtension.Length >= MaxPathLength) + if (logOutputDirectory.Length + testName.Length + LogFileExtension.Length >= MaxPathLength) { _globalLogger.LogWarning($"Test name {testName} is too long. Please shorten test name."); // Shorten the test name by removing the middle portion of the testname - var testNameLength = MaxPathLength - testOutputDirectory.Length - LogFileExtension.Length; + var testNameLength = MaxPathLength - logOutputDirectory.Length - LogFileExtension.Length; if (testNameLength <= 0) { @@ -129,7 +133,7 @@ namespace Microsoft.Extensions.Logging.Testing _globalLogger.LogWarning($"To prevent long paths test name was shortened to {testName}."); } - var testOutputFile = Path.Combine(testOutputDirectory, $"{testName}{LogFileExtension}"); + var testOutputFile = Path.Combine(logOutputDirectory, $"{testName}{LogFileExtension}"); if (File.Exists(testOutputFile)) { @@ -137,7 +141,7 @@ namespace Microsoft.Extensions.Logging.Testing for (var i = 0; i < 1000; i++) { - testOutputFile = Path.Combine(testOutputDirectory, $"{testName}.{i}{LogFileExtension}"); + testOutputFile = Path.Combine(logOutputDirectory, $"{testName}.{i}{LogFileExtension}"); if (!File.Exists(testOutputFile)) { @@ -149,7 +153,7 @@ namespace Microsoft.Extensions.Logging.Testing } normalizedTestName = testName; - serilogLoggerProvider = ConfigureFileLogging(testOutputFile); + serilogLoggerProvider = ConfigureFileLogging(testOutputFile, logStart); } var serviceCollection = new ServiceCollection(); @@ -159,7 +163,7 @@ namespace Microsoft.Extensions.Logging.Testing if (output != null) { - builder.AddXunit(output, minLogLevel); + builder.AddXunit(output, minLogLevel, logStart); } if (serilogLoggerProvider != null) @@ -172,14 +176,19 @@ namespace Microsoft.Extensions.Logging.Testing return serviceCollection.BuildServiceProvider(); } + // For back compat public static AssemblyTestLog Create(string assemblyName, string baseDirectory) + => Create(Assembly.Load(new AssemblyName(assemblyName)), baseDirectory); + + public static AssemblyTestLog Create(Assembly assembly, string baseDirectory) { + var logStart = DateTimeOffset.UtcNow; SerilogLoggerProvider serilogLoggerProvider = null; - var globalLogDirectory = GetAssemblyBaseDirectory(assemblyName, baseDirectory); + var globalLogDirectory = GetAssemblyBaseDirectory(baseDirectory, assembly); if (!string.IsNullOrEmpty(globalLogDirectory)) { var globalLogFileName = Path.Combine(globalLogDirectory, "global.log"); - serilogLoggerProvider = ConfigureFileLogging(globalLogFileName); + serilogLoggerProvider = ConfigureFileLogging(globalLogFileName, logStart); } var serviceCollection = new ServiceCollection(); @@ -200,8 +209,11 @@ namespace Microsoft.Extensions.Logging.Testing var loggerFactory = serviceProvider.GetRequiredService(); var logger = loggerFactory.CreateLogger("GlobalTestLog"); - logger.LogInformation($"Global Test Logging initialized. Set the '{OutputDirectoryEnvironmentVariableName}' Environment Variable in order to create log files on disk."); - return new AssemblyTestLog(loggerFactory, logger, baseDirectory, assemblyName, serviceProvider); + logger.LogInformation("Global Test Logging initialized at {logStart}. " + + "Configure the output directory via 'LoggingTestingFileLoggingDirectory' MSBuild property " + + "or set 'LoggingTestingDisableFileLogging' to 'true' to disable file logging.", + logStart.ToString("s")); + return new AssemblyTestLog(loggerFactory, logger, baseDirectory, assembly, serviceProvider); } public static AssemblyTestLog ForAssembly(Assembly assembly) @@ -210,13 +222,13 @@ namespace Microsoft.Extensions.Logging.Testing { if (!_logs.TryGetValue(assembly, out var log)) { - var assemblyName = assembly.GetName().Name; - var baseDirectory = Environment.GetEnvironmentVariable(OutputDirectoryEnvironmentVariableName); - log = Create(assemblyName, baseDirectory); + var baseDirectory = GetFileLoggerAttribute(assembly).BaseDirectory; + + log = Create(assembly, baseDirectory); _logs[assembly] = log; // Try to clear previous logs - var assemblyBaseDirectory = GetAssemblyBaseDirectory(assemblyName, baseDirectory); + var assemblyBaseDirectory = GetAssemblyBaseDirectory(baseDirectory, assembly); if (Directory.Exists(assemblyBaseDirectory)) { try @@ -230,16 +242,18 @@ namespace Microsoft.Extensions.Logging.Testing } } - private static string GetAssemblyBaseDirectory(string assemblyName, string baseDirectory) - { - if (!string.IsNullOrEmpty(baseDirectory)) - { - return Path.Combine(baseDirectory, assemblyName, RuntimeInformation.FrameworkDescription.TrimStart('.')); - } - return string.Empty; - } + private static string GetAssemblyBaseDirectory(string baseDirectory, Assembly assembly) + => string.IsNullOrEmpty(baseDirectory) + ? string.Empty + : Path.Combine(baseDirectory, assembly.GetName().Name, GetFileLoggerAttribute(assembly).TFM); - private static SerilogLoggerProvider ConfigureFileLogging(string fileName) + private static TestFrameworkFileLoggerAttribute GetFileLoggerAttribute(Assembly assembly) + => assembly.GetCustomAttribute() + ?? throw new InvalidOperationException($"No {nameof(TestFrameworkFileLoggerAttribute)} found on the assembly {assembly.GetName().Name}. " + + "The attribute is added via msbuild properties of the Microsoft.Extensions.Logging.Testing. " + + "Please ensure the msbuild property is imported or a direct reference to Microsoft.Extensions.Logging.Testing is added."); + + private static SerilogLoggerProvider ConfigureFileLogging(string fileName, DateTimeOffset? logStart) { var dir = Path.GetDirectoryName(fileName); if (!Directory.Exists(dir)) @@ -254,8 +268,9 @@ namespace Microsoft.Extensions.Logging.Testing var serilogger = new LoggerConfiguration() .Enrich.FromLogContext() + .Enrich.With(new AssemblyLogTimestampOffsetEnricher(logStart)) .MinimumLevel.Verbose() - .WriteTo.File(fileName, outputTemplate: "{Timestamp:yyyy-MM-dd HH:mm:ss.fff zzz} [{SourceContext}] [{Level}] {Message}{NewLine}{Exception}", flushToDiskInterval: TimeSpan.FromSeconds(1), shared: true) + .WriteTo.File(fileName, outputTemplate: "[{TimestampOffset}] [{SourceContext}] [{Level}] {Message:l}{NewLine}{Exception}", flushToDiskInterval: TimeSpan.FromSeconds(1), shared: true) .CreateLogger(); return new SerilogLoggerProvider(serilogger, dispose: true); } @@ -287,6 +302,24 @@ namespace Microsoft.Extensions.Logging.Testing _globalLoggerFactory.Dispose(); } + private class AssemblyLogTimestampOffsetEnricher : ILogEventEnricher + { + private DateTimeOffset? _logStart; + + public AssemblyLogTimestampOffsetEnricher(DateTimeOffset? logStart) + { + _logStart = logStart; + } + + public void Enrich(LogEvent logEvent, ILogEventPropertyFactory propertyFactory) + => logEvent.AddPropertyIfAbsent( + propertyFactory.CreateProperty( + "TimestampOffset", + _logStart.HasValue + ? $"{(DateTimeOffset.UtcNow - _logStart.Value).TotalSeconds.ToString("N3")}s" + : DateTimeOffset.UtcNow.ToString("s"))); + } + private class Disposable : IDisposable { private Action _action; diff --git a/src/Testing/src/LoggedTest/ILoggedTest.cs b/src/Testing/src/LoggedTest/ILoggedTest.cs new file mode 100644 index 0000000000..a563cbdaf9 --- /dev/null +++ b/src/Testing/src/LoggedTest/ILoggedTest.cs @@ -0,0 +1,23 @@ +// 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.Reflection; +using Xunit.Abstractions; + +namespace Microsoft.Extensions.Logging.Testing +{ + public interface ILoggedTest : IDisposable + { + ILogger Logger { get; } + + ILoggerFactory LoggerFactory { get; } + + ITestOutputHelper TestOutputHelper { get; } + + // For back compat + IDisposable StartLog(out ILoggerFactory loggerFactory, LogLevel minLogLevel, string testName); + + void Initialize(MethodInfo methodInfo, object[] testMethodArguments, ITestOutputHelper testOutputHelper); + } +} diff --git a/src/Testing/src/LoggedTest/LoggedTest.cs b/src/Testing/src/LoggedTest/LoggedTest.cs new file mode 100644 index 0000000000..64a9adec06 --- /dev/null +++ b/src/Testing/src/LoggedTest/LoggedTest.cs @@ -0,0 +1,24 @@ +// 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.Reflection; +using Xunit.Abstractions; + +namespace Microsoft.Extensions.Logging.Testing +{ + public class LoggedTest : LoggedTestBase + { + // Obsolete but keeping for back compat + public LoggedTest(ITestOutputHelper output = null) : base (output) { } + + public ITestSink TestSink { get; set; } + + public override void Initialize(MethodInfo methodInfo, object[] testMethodArguments, ITestOutputHelper testOutputHelper) + { + base.Initialize(methodInfo, testMethodArguments, testOutputHelper); + + TestSink = new TestSink(); + LoggerFactory.AddProvider(new TestLoggerProvider(TestSink)); + } + } +} diff --git a/src/Testing/src/LoggedTest/LoggedTestBase.cs b/src/Testing/src/LoggedTest/LoggedTestBase.cs new file mode 100644 index 0000000000..f714a632a4 --- /dev/null +++ b/src/Testing/src/LoggedTest/LoggedTestBase.cs @@ -0,0 +1,82 @@ +// 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.Linq; +using System.Reflection; +using System.Runtime.CompilerServices; +using Microsoft.Extensions.DependencyInjection; +using Xunit.Abstractions; + +namespace Microsoft.Extensions.Logging.Testing +{ + public class LoggedTestBase : ILoggedTest + { + private IDisposable _testLog; + + // Obsolete but keeping for back compat + public LoggedTestBase(ITestOutputHelper output = null) + { + TestOutputHelper = output; + } + + // Internal for testing + internal string ResolvedTestClassName { get; set; } + + internal RetryContext RetryContext { get; set; } + + public string ResolvedLogOutputDirectory { get; set; } + + public string ResolvedTestMethodName { get; set; } + + public ILogger Logger { get; set; } + + public ILoggerFactory LoggerFactory { get; set; } + + public ITestOutputHelper TestOutputHelper { get; set; } + + public void AddTestLogging(IServiceCollection services) => services.AddSingleton(LoggerFactory); + + // For back compat + public IDisposable StartLog(out ILoggerFactory loggerFactory, [CallerMemberName] string testName = null) => StartLog(out loggerFactory, LogLevel.Debug, testName); + + // For back compat + public IDisposable StartLog(out ILoggerFactory loggerFactory, LogLevel minLogLevel, [CallerMemberName] string testName = null) + { + return AssemblyTestLog.ForAssembly(GetType().GetTypeInfo().Assembly).StartTestLog(TestOutputHelper, GetType().FullName, out loggerFactory, minLogLevel, testName); + } + + public virtual void Initialize(MethodInfo methodInfo, object[] testMethodArguments, ITestOutputHelper testOutputHelper) + { + TestOutputHelper = testOutputHelper; + + var classType = GetType(); + var logLevelAttribute = methodInfo.GetCustomAttribute(); + var testName = testMethodArguments.Aggregate(methodInfo.Name, (a, b) => $"{a}-{(b ?? "null")}"); + + var useShortClassName = methodInfo.DeclaringType.GetCustomAttribute() + ?? methodInfo.DeclaringType.Assembly.GetCustomAttribute(); + // internal for testing + ResolvedTestClassName = useShortClassName == null ? classType.FullName : classType.Name; + + _testLog = AssemblyTestLog + .ForAssembly(classType.GetTypeInfo().Assembly) + .StartTestLog( + TestOutputHelper, + ResolvedTestClassName, + out var loggerFactory, + logLevelAttribute?.LogLevel ?? LogLevel.Debug, + out var resolvedTestName, + out var logOutputDirectory, + testName); + + ResolvedLogOutputDirectory = logOutputDirectory; + ResolvedTestMethodName = resolvedTestName; + + LoggerFactory = loggerFactory; + Logger = loggerFactory.CreateLogger(classType); + } + + public virtual void Dispose() => _testLog.Dispose(); + } +} diff --git a/src/Testing/src/TestFrameworkFileLoggerAttribute.cs b/src/Testing/src/TestFrameworkFileLoggerAttribute.cs new file mode 100644 index 0000000000..32d8f30584 --- /dev/null +++ b/src/Testing/src/TestFrameworkFileLoggerAttribute.cs @@ -0,0 +1,20 @@ +// 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; + +namespace Microsoft.Extensions.Logging.Testing +{ + [AttributeUsage(AttributeTargets.Assembly, AllowMultiple = false)] + public class TestFrameworkFileLoggerAttribute : Attribute + { + public TestFrameworkFileLoggerAttribute(string tfm, string baseDirectory = null) + { + TFM = tfm; + BaseDirectory = baseDirectory; + } + + public string TFM { get; } + public string BaseDirectory { get; } + } +} \ No newline at end of file diff --git a/src/Testing/src/build/Microsoft.Extensions.Logging.Testing.props b/src/Testing/src/build/Microsoft.Extensions.Logging.Testing.props index f98e3e13b5..0d2585146c 100644 --- a/src/Testing/src/build/Microsoft.Extensions.Logging.Testing.props +++ b/src/Testing/src/build/Microsoft.Extensions.Logging.Testing.props @@ -1,8 +1,23 @@  - - - <_Parameter1>Microsoft.Extensions.Logging.Testing.LoggedTestFramework - <_Parameter2>Microsoft.Extensions.Logging.Testing - - + + + $(ASPNETCORE_TEST_LOG_DIR) + $(RepositoryRoot)artifacts\logs\ + + + + + + <_Parameter1>Microsoft.Extensions.Logging.Testing.LoggedTestFramework + <_Parameter2>Microsoft.Extensions.Logging.Testing + + + + <_Parameter1>$(TargetFramework) + <_Parameter2 Condition="'$(LoggingTestingDisableFileLogging)' != 'true'">$(LoggingTestingFileLoggingDirectory) + + + \ No newline at end of file diff --git a/src/Testing/test/AssemblyTestLogTests.cs b/src/Testing/test/AssemblyTestLogTests.cs index 0efadb4367..20f597defc 100644 --- a/src/Testing/test/AssemblyTestLogTests.cs +++ b/src/Testing/test/AssemblyTestLogTests.cs @@ -6,7 +6,6 @@ using System.IO; using System.Linq; using System.Reflection; using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; using System.Text.RegularExpressions; using System.Threading.Tasks; using Xunit; @@ -15,7 +14,9 @@ namespace Microsoft.Extensions.Logging.Testing.Tests { public class AssemblyTestLogTests : LoggedTest { - private static readonly Assembly ThisAssembly = typeof(AssemblyTestLog).GetTypeInfo().Assembly; + private static readonly Assembly ThisAssembly = typeof(AssemblyTestLogTests).GetTypeInfo().Assembly; + private static readonly string ThisAssemblyName = ThisAssembly.GetName().Name; + private static readonly string TFM = new DirectoryInfo(AppContext.BaseDirectory).Name; [Fact] public void FullClassNameUsedWhenShortClassNameAttributeNotSpecified() @@ -35,7 +36,7 @@ namespace Microsoft.Extensions.Logging.Testing.Tests public void TestLogWritesToITestOutputHelper() { var output = new TestTestOutputHelper(); - var assemblyLog = AssemblyTestLog.Create("NonExistant.Test.Assembly", baseDirectory: null); + var assemblyLog = AssemblyTestLog.Create(ThisAssemblyName, baseDirectory: null); using (assemblyLog.StartTestLog(output, "NonExistant.Test.Class", out var loggerFactory)) { @@ -46,20 +47,23 @@ namespace Microsoft.Extensions.Logging.Testing.Tests logger.LogTrace("Trace!"); } - Assert.Equal(@"[TIMESTAMP] TestLifetime Information: Starting test TestLogWritesToITestOutputHelper -[TIMESTAMP] TestLogger Information: Information! -[TIMESTAMP] TestLifetime Information: Finished test TestLogWritesToITestOutputHelper in DURATION -", MakeConsistent(output.Output), ignoreLineEndingDifferences: true); + var testLogContent = MakeConsistent(output.Output); + + Assert.Equal( +@"[OFFSET] TestLifetime Information: Starting test TestLogWritesToITestOutputHelper at TIMESTAMP +[OFFSET] TestLogger Information: Information! +[OFFSET] TestLifetime Information: Finished test TestLogWritesToITestOutputHelper in DURATION +", testLogContent, ignoreLineEndingDifferences: true); } [Fact] private Task TestLogEscapesIllegalFileNames() => RunTestLogFunctionalTest((tempDir) => { - var illegalTestName = "Testing-https://localhost:5000"; - var escapedTestName = "Testing-https_localhost_5000"; - using (var testAssemblyLog = AssemblyTestLog.Create("FakeTestAssembly", baseDirectory: tempDir)) - using (testAssemblyLog.StartTestLog(output: null, className: "FakeTestAssembly.FakeTestClass", loggerFactory: out var testLoggerFactory, minLogLevel: LogLevel.Trace, resolvedTestName: out var resolvedTestname, testName: illegalTestName)) + var illegalTestName = "T:e/s//t"; + var escapedTestName = "T_e_s_t"; + using (var testAssemblyLog = AssemblyTestLog.Create(ThisAssemblyName, baseDirectory: tempDir)) + using (testAssemblyLog.StartTestLog(output: null, className: "FakeTestAssembly.FakeTestClass", loggerFactory: out var testLoggerFactory, minLogLevel: LogLevel.Trace, resolvedTestName: out var resolvedTestname, out var _, testName: illegalTestName)) { Assert.Equal(escapedTestName, resolvedTestname); } @@ -73,11 +77,11 @@ namespace Microsoft.Extensions.Logging.Testing.Tests // but it's also testing the test logging facility. So this is pretty meta ;) var logger = LoggerFactory.CreateLogger("Test"); - using (var testAssemblyLog = AssemblyTestLog.Create("FakeTestAssembly", tempDir)) + using (var testAssemblyLog = AssemblyTestLog.Create(ThisAssemblyName, tempDir)) { logger.LogInformation("Created test log in {baseDirectory}", tempDir); - using (testAssemblyLog.StartTestLog(output: null, className: "FakeTestAssembly.FakeTestClass", loggerFactory: out var testLoggerFactory, minLogLevel: LogLevel.Trace, testName: "FakeTestName")) + using (testAssemblyLog.StartTestLog(output: null, className: $"{ThisAssemblyName}.FakeTestClass", loggerFactory: out var testLoggerFactory, minLogLevel: LogLevel.Trace, testName: "FakeTestName")) { var testLogger = testLoggerFactory.CreateLogger("TestLogger"); testLogger.LogInformation("Information!"); @@ -87,8 +91,8 @@ namespace Microsoft.Extensions.Logging.Testing.Tests logger.LogInformation("Finished test log in {baseDirectory}", tempDir); - var globalLogPath = Path.Combine(tempDir, "FakeTestAssembly", RuntimeInformation.FrameworkDescription.TrimStart('.'), "global.log"); - var testLog = Path.Combine(tempDir, "FakeTestAssembly", RuntimeInformation.FrameworkDescription.TrimStart('.'), "FakeTestClass", $"FakeTestName.log"); + var globalLogPath = Path.Combine(tempDir, ThisAssemblyName, TFM, "global.log"); + var testLog = Path.Combine(tempDir, ThisAssemblyName, TFM, "FakeTestClass", "FakeTestName.log"); Assert.True(File.Exists(globalLogPath), $"Expected global log file {globalLogPath} to exist"); Assert.True(File.Exists(testLog), $"Expected test log file {testLog} to exist"); @@ -96,14 +100,16 @@ namespace Microsoft.Extensions.Logging.Testing.Tests var globalLogContent = MakeConsistent(File.ReadAllText(globalLogPath)); var testLogContent = MakeConsistent(File.ReadAllText(testLog)); - Assert.Equal(@"[GlobalTestLog] [Information] Global Test Logging initialized. Set the 'ASPNETCORE_TEST_LOG_DIR' Environment Variable in order to create log files on disk. -[GlobalTestLog] [Information] Starting test ""FakeTestName"" -[GlobalTestLog] [Information] Finished test ""FakeTestName"" in DURATION + Assert.Equal( +@"[OFFSET] [GlobalTestLog] [Information] Global Test Logging initialized at TIMESTAMP. Configure the output directory via 'LoggingTestingFileLoggingDirectory' MSBuild property or set 'LoggingTestingDisableFileLogging' to 'true' to disable file logging. +[OFFSET] [GlobalTestLog] [Information] Starting test FakeTestName +[OFFSET] [GlobalTestLog] [Information] Finished test FakeTestName in DURATION ", globalLogContent, ignoreLineEndingDifferences: true); - Assert.Equal(@"[TestLifetime] [Information] Starting test ""FakeTestName"" -[TestLogger] [Information] Information! -[TestLogger] [Verbose] Trace! -[TestLifetime] [Information] Finished test ""FakeTestName"" in DURATION + Assert.Equal( +@"[OFFSET] [TestLifetime] [Information] Starting test FakeTestName at TIMESTAMP +[OFFSET] [TestLogger] [Information] Information! +[OFFSET] [TestLogger] [Verbose] Trace! +[OFFSET] [TestLifetime] [Information] Finished test FakeTestName in DURATION ", testLogContent, ignoreLineEndingDifferences: true); }); @@ -113,18 +119,18 @@ namespace Microsoft.Extensions.Logging.Testing.Tests { var longTestName = new string('0', 50) + new string('1', 50) + new string('2', 50) + new string('3', 50) + new string('4', 50); var logger = LoggerFactory.CreateLogger("Test"); - using (var testAssemblyLog = AssemblyTestLog.Create("FakeTestAssembly", tempDir)) + using (var testAssemblyLog = AssemblyTestLog.Create(ThisAssemblyName, tempDir)) { logger.LogInformation("Created test log in {baseDirectory}", tempDir); - using (testAssemblyLog.StartTestLog(output: null, className: "FakeTestAssembly.FakeTestClass", loggerFactory: out var testLoggerFactory, minLogLevel: LogLevel.Trace, testName: longTestName)) + using (testAssemblyLog.StartTestLog(output: null, className: $"{ThisAssemblyName}.FakeTestClass", loggerFactory: out var testLoggerFactory, minLogLevel: LogLevel.Trace, testName: longTestName)) { testLoggerFactory.CreateLogger("TestLogger").LogInformation("Information!"); } } logger.LogInformation("Finished test log in {baseDirectory}", tempDir); - var testLogFiles = new DirectoryInfo(Path.Combine(tempDir, "FakeTestAssembly", RuntimeInformation.FrameworkDescription.TrimStart('.'), "FakeTestClass")).EnumerateFiles(); + var testLogFiles = new DirectoryInfo(Path.Combine(tempDir, ThisAssemblyName, TFM, "FakeTestClass")).EnumerateFiles(); var testLog = Assert.Single(testLogFiles); var testFileName = Path.GetFileNameWithoutExtension(testLog.Name); @@ -139,13 +145,13 @@ namespace Microsoft.Extensions.Logging.Testing.Tests RunTestLogFunctionalTest((tempDir) => { var logger = LoggerFactory.CreateLogger("Test"); - using (var testAssemblyLog = AssemblyTestLog.Create("FakeTestAssembly", tempDir)) + using (var testAssemblyLog = AssemblyTestLog.Create(ThisAssemblyName, tempDir)) { logger.LogInformation("Created test log in {baseDirectory}", tempDir); for (var i = 0; i < 10; i++) { - using (testAssemblyLog.StartTestLog(output: null, className: "FakeTestAssembly.FakeTestClass", loggerFactory: out var testLoggerFactory, minLogLevel: LogLevel.Trace, testName: "FakeTestName")) + using (testAssemblyLog.StartTestLog(output: null, className: $"{ThisAssemblyName}.FakeTestClass", loggerFactory: out var testLoggerFactory, minLogLevel: LogLevel.Trace, testName: "FakeTestName")) { testLoggerFactory.CreateLogger("TestLogger").LogInformation("Information!"); } @@ -154,16 +160,17 @@ namespace Microsoft.Extensions.Logging.Testing.Tests logger.LogInformation("Finished test log in {baseDirectory}", tempDir); // The first log file exists - Assert.True(File.Exists(Path.Combine(tempDir, "FakeTestAssembly", RuntimeInformation.FrameworkDescription.TrimStart('.'), "FakeTestClass", $"FakeTestName.log"))); + Assert.True(File.Exists(Path.Combine(tempDir, ThisAssemblyName, TFM, "FakeTestClass", "FakeTestName.log"))); // Subsequent files exist for (var i = 0; i < 9; i++) { - Assert.True(File.Exists(Path.Combine(tempDir, "FakeTestAssembly", RuntimeInformation.FrameworkDescription.TrimStart('.'), "FakeTestClass", $"FakeTestName.{i}.log"))); + Assert.True(File.Exists(Path.Combine(tempDir, ThisAssemblyName, TFM, "FakeTestClass", $"FakeTestName.{i}.log"))); } }); private static readonly Regex TimestampRegex = new Regex(@"\d+-\d+-\d+T\d+:\d+:\d+"); + private static readonly Regex TimestampOffsetRegex = new Regex(@"\d+\.\d+s"); private static readonly Regex DurationRegex = new Regex(@"[^ ]+s$"); private async Task RunTestLogFunctionalTest(Action action, [CallerMemberName] string testName = null) @@ -197,10 +204,10 @@ namespace Microsoft.Extensions.Logging.Testing.Tests { var strippedPrefix = line.IndexOf("[") >= 0 ? line.Substring(line.IndexOf("[")) : line; - var strippedDuration = - DurationRegex.Replace(strippedPrefix, "DURATION"); + var strippedDuration = DurationRegex.Replace(strippedPrefix, "DURATION"); var strippedTimestamp = TimestampRegex.Replace(strippedDuration, "TIMESTAMP"); - return strippedTimestamp; + var strippedTimestampOffset = TimestampOffsetRegex.Replace(strippedTimestamp, "OFFSET"); + return strippedTimestampOffset; })); } } diff --git a/src/Testing/test/LoggedTestXunitTests.cs b/src/Testing/test/LoggedTestXunitTests.cs index 31fd6d631f..d1d8581193 100644 --- a/src/Testing/test/LoggedTestXunitTests.cs +++ b/src/Testing/test/LoggedTestXunitTests.cs @@ -1,6 +1,7 @@ // 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.Reflection; using Microsoft.AspNetCore.Testing.xunit; using Microsoft.Extensions.DependencyInjection; using Xunit; @@ -130,12 +131,29 @@ namespace Microsoft.Extensions.Logging.Testing.Tests } } + public class LoggedTestXunitInitializationTests : TestLoggedTest + { + [Fact] + public void ITestOutputHelperInitializedByDefault() + { + Assert.True(ITestOutputHelperIsInitialized); + } + } + public class TestLoggedTest : LoggedTest { public bool SetupInvoked { get; private set; } = false; + public bool ITestOutputHelperIsInitialized { get; private set; } = false; - public override void AdditionalSetup() + public override void Initialize(MethodInfo methodInfo, object[] testMethodArguments, ITestOutputHelper testOutputHelper) { + base.Initialize(methodInfo, testMethodArguments, testOutputHelper); + + try + { + TestOutputHelper.WriteLine("Test"); + ITestOutputHelperIsInitialized = true; + } catch { } SetupInvoked = true; } } From e7cca176e589b9102cf43365f2bca3d356fb29aa Mon Sep 17 00:00:00 2001 From: Nate McMaster Date: Tue, 6 Nov 2018 16:58:30 -0800 Subject: [PATCH 0014/1101] Reorganize source code in preparation to move into aspnet/Extensions Prior to reorganization, this source code was found in https://github.com/aspnet/Logging/tree/f7d8e4e0537eaab54dcf28c2b148b82688a3d62d --- src/Testing/src/LoggedTest/LoggedTestBase.cs | 4 +- src/Testing/test/LoggedTestXunitTests.cs | 46 +++++++++++++++++++- 2 files changed, 48 insertions(+), 2 deletions(-) diff --git a/src/Testing/src/LoggedTest/LoggedTestBase.cs b/src/Testing/src/LoggedTest/LoggedTestBase.cs index f714a632a4..c3a4e78931 100644 --- a/src/Testing/src/LoggedTest/LoggedTestBase.cs +++ b/src/Testing/src/LoggedTest/LoggedTestBase.cs @@ -51,7 +51,9 @@ namespace Microsoft.Extensions.Logging.Testing TestOutputHelper = testOutputHelper; var classType = GetType(); - var logLevelAttribute = methodInfo.GetCustomAttribute(); + var logLevelAttribute = methodInfo.GetCustomAttribute() + ?? methodInfo.DeclaringType.GetCustomAttribute() + ?? methodInfo.DeclaringType.Assembly.GetCustomAttribute(); var testName = testMethodArguments.Aggregate(methodInfo.Name, (a, b) => $"{a}-{(b ?? "null")}"); var useShortClassName = methodInfo.DeclaringType.GetCustomAttribute() diff --git a/src/Testing/test/LoggedTestXunitTests.cs b/src/Testing/test/LoggedTestXunitTests.cs index d1d8581193..507453a242 100644 --- a/src/Testing/test/LoggedTestXunitTests.cs +++ b/src/Testing/test/LoggedTestXunitTests.cs @@ -1,6 +1,7 @@ // 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.Linq; using System.Reflection; using Microsoft.AspNetCore.Testing.xunit; using Microsoft.Extensions.DependencyInjection; @@ -9,6 +10,7 @@ using Xunit.Abstractions; namespace Microsoft.Extensions.Logging.Testing.Tests { + [LogLevel(LogLevel.Debug)] [ShortClassName] public class LoggedTestXunitTests : TestLoggedTest { @@ -75,7 +77,7 @@ namespace Microsoft.Extensions.Logging.Testing.Tests [Fact] [LogLevel(LogLevel.Information)] - public void LoggedFactFilteredByLogLevel() + public void LoggedFactFilteredByMethodLogLevel() { Logger.LogInformation("Information"); Logger.LogDebug("Debug"); @@ -85,6 +87,17 @@ namespace Microsoft.Extensions.Logging.Testing.Tests Assert.Equal("Information", message.Formatter(message.State, null)); } + [Fact] + public void LoggedFactFilteredByClassLogLevel() + { + Logger.LogDebug("Debug"); + Logger.LogTrace("Trace"); + + var message = Assert.Single(TestSink.Writes); + Assert.Equal(LogLevel.Debug, message.LogLevel); + Assert.Equal("Debug", message.Formatter(message.State, null)); + } + [Theory] [InlineData("Hello world")] [LogLevel(LogLevel.Information)] @@ -129,6 +142,37 @@ namespace Microsoft.Extensions.Logging.Testing.Tests { Assert.True(SetupInvoked); } + + [Fact] + public void MessageWrittenEventInvoked() + { + WriteContext context = null; + TestSink.MessageLogged += ctx => context = ctx; + Logger.LogInformation("Information"); + Assert.Equal(TestSink.Writes.Single(), context); + } + + [Fact] + public void ScopeStartedEventInvoked() + { + BeginScopeContext context = null; + TestSink.ScopeStarted += ctx => context = ctx; + using (Logger.BeginScope("Scope")) {} + Assert.Equal(TestSink.Scopes.Single(), context); + } + } + + public class LoggedTestXunitLogLevelTests : LoggedTest + { + [Fact] + public void LoggedFactFilteredByAssemblyLogLevel() + { + Logger.LogTrace("Trace"); + + var message = Assert.Single(TestSink.Writes); + Assert.Equal(LogLevel.Trace, message.LogLevel); + Assert.Equal("Trace", message.Formatter(message.State, null)); + } } public class LoggedTestXunitInitializationTests : TestLoggedTest From aa26d515be7c279263919b2c547d2153f6c5f88b Mon Sep 17 00:00:00 2001 From: Nate McMaster Date: Fri, 9 Nov 2018 15:53:41 -0800 Subject: [PATCH 0015/1101] Reorganize source code in preparation to move into aspnet/Extensions Prior to reorganization, this source code was found in https://github.com/aspnet/HtmlAbstractions/tree/dotnet/extensions@252ae0c434d93f5bfaf949c35edb9ef59730d0a3 \n\nCommit migrated from https://github.com/dotnet/extensions/commit/6c2bf2943469b133f25cb84363358cca55058466 --- .../src/EncoderServiceCollectionExtensions.cs | 83 +++ .../Microsoft.Extensions.WebEncoders.csproj | 18 + .../src/Testing/HtmlTestEncoder.cs | 104 ++++ .../src/Testing/JavaScriptTestEncoder.cs | 104 ++++ src/WebEncoders/src/Testing/UrlTestEncoder.cs | 104 ++++ src/WebEncoders/src/WebEncoderOptions.cs | 21 + src/WebEncoders/src/baseline.netcore.json | 564 ++++++++++++++++++ ...EncoderServiceCollectionExtensionsTests.cs | 90 +++ src/WebEncoders/test/HtmlTestEncoderTest.cs | 26 + ...rosoft.Extensions.WebEncoders.Tests.csproj | 12 + 10 files changed, 1126 insertions(+) create mode 100644 src/WebEncoders/src/EncoderServiceCollectionExtensions.cs create mode 100644 src/WebEncoders/src/Microsoft.Extensions.WebEncoders.csproj create mode 100644 src/WebEncoders/src/Testing/HtmlTestEncoder.cs create mode 100644 src/WebEncoders/src/Testing/JavaScriptTestEncoder.cs create mode 100644 src/WebEncoders/src/Testing/UrlTestEncoder.cs create mode 100644 src/WebEncoders/src/WebEncoderOptions.cs create mode 100644 src/WebEncoders/src/baseline.netcore.json create mode 100644 src/WebEncoders/test/EncoderServiceCollectionExtensionsTests.cs create mode 100644 src/WebEncoders/test/HtmlTestEncoderTest.cs create mode 100755 src/WebEncoders/test/Microsoft.Extensions.WebEncoders.Tests.csproj diff --git a/src/WebEncoders/src/EncoderServiceCollectionExtensions.cs b/src/WebEncoders/src/EncoderServiceCollectionExtensions.cs new file mode 100644 index 0000000000..72f5e369a1 --- /dev/null +++ b/src/WebEncoders/src/EncoderServiceCollectionExtensions.cs @@ -0,0 +1,83 @@ +// 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.Text.Encodings.Web; +using Microsoft.Extensions.DependencyInjection.Extensions; +using Microsoft.Extensions.Options; +using Microsoft.Extensions.WebEncoders; + +namespace Microsoft.Extensions.DependencyInjection +{ + /// + /// Extension methods for setting up web encoding services in an . + /// + public static class EncoderServiceCollectionExtensions + { + /// + /// Adds , and + /// to the specified . + /// + /// The . + /// The so that additional calls can be chained. + public static IServiceCollection AddWebEncoders(this IServiceCollection services) + { + if (services == null) + { + throw new ArgumentNullException(nameof(services)); + } + + services.AddOptions(); + + // Register the default encoders + // We want to call the 'Default' property getters lazily since they perform static caching + services.TryAddSingleton( + CreateFactory(() => HtmlEncoder.Default, settings => HtmlEncoder.Create(settings))); + services.TryAddSingleton( + CreateFactory(() => JavaScriptEncoder.Default, settings => JavaScriptEncoder.Create(settings))); + services.TryAddSingleton( + CreateFactory(() => UrlEncoder.Default, settings => UrlEncoder.Create(settings))); + + return services; + } + + /// + /// Adds , and + /// to the specified . + /// + /// The . + /// An to configure the provided . + /// The so that additional calls can be chained. + public static IServiceCollection AddWebEncoders(this IServiceCollection services, Action setupAction) + { + if (services == null) + { + throw new ArgumentNullException(nameof(services)); + } + + if (setupAction == null) + { + throw new ArgumentNullException(nameof(setupAction)); + } + + services.AddWebEncoders(); + services.Configure(setupAction); + + return services; + } + + private static Func CreateFactory( + Func defaultFactory, + Func customSettingsFactory) + { + return serviceProvider => + { + var settings = serviceProvider + ?.GetService>() + ?.Value + ?.TextEncoderSettings; + return (settings != null) ? customSettingsFactory(settings) : defaultFactory(); + }; + } + } +} diff --git a/src/WebEncoders/src/Microsoft.Extensions.WebEncoders.csproj b/src/WebEncoders/src/Microsoft.Extensions.WebEncoders.csproj new file mode 100644 index 0000000000..18f96d9412 --- /dev/null +++ b/src/WebEncoders/src/Microsoft.Extensions.WebEncoders.csproj @@ -0,0 +1,18 @@ + + + + Contains registration and configuration APIs to add the core framework encoders to a dependency injection container. + netstandard2.0 + $(NoWarn);CS1591 + true + true + aspnetcore + + + + + + + + + diff --git a/src/WebEncoders/src/Testing/HtmlTestEncoder.cs b/src/WebEncoders/src/Testing/HtmlTestEncoder.cs new file mode 100644 index 0000000000..162ce4f6c1 --- /dev/null +++ b/src/WebEncoders/src/Testing/HtmlTestEncoder.cs @@ -0,0 +1,104 @@ +// 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.IO; +using System.Text.Encodings.Web; + +namespace Microsoft.Extensions.WebEncoders.Testing +{ + /// + /// Encoder used for unit testing. + /// + public sealed class HtmlTestEncoder : HtmlEncoder + { + public override int MaxOutputCharactersPerInputCharacter + { + get { return 1; } + } + + public override string Encode(string value) + { + if (value == null) + { + throw new ArgumentNullException(nameof(value)); + } + + if (value.Length == 0) + { + return string.Empty; + } + + return $"HtmlEncode[[{value}]]"; + } + + public override void Encode(TextWriter output, char[] value, int startIndex, int characterCount) + { + if (output == null) + { + throw new ArgumentNullException(nameof(output)); + } + + if (value == null) + { + throw new ArgumentNullException(nameof(value)); + } + + if (characterCount == 0) + { + return; + } + + output.Write("HtmlEncode[["); + output.Write(value, startIndex, characterCount); + output.Write("]]"); + } + + public override void Encode(TextWriter output, string value, int startIndex, int characterCount) + { + if (output == null) + { + throw new ArgumentNullException(nameof(output)); + } + + if (value == null) + { + throw new ArgumentNullException(nameof(value)); + } + + if (characterCount == 0) + { + return; + } + + output.Write("HtmlEncode[["); + output.Write(value.Substring(startIndex, characterCount)); + output.Write("]]"); + } + + public override bool WillEncode(int unicodeScalar) + { + return false; + } + + public override unsafe int FindFirstCharacterToEncode(char* text, int textLength) + { + return -1; + } + + public override unsafe bool TryEncodeUnicodeScalar( + int unicodeScalar, + char* buffer, + int bufferLength, + out int numberOfCharactersWritten) + { + if (buffer == null) + { + throw new ArgumentNullException(nameof(buffer)); + } + + numberOfCharactersWritten = 0; + return false; + } + } +} \ No newline at end of file diff --git a/src/WebEncoders/src/Testing/JavaScriptTestEncoder.cs b/src/WebEncoders/src/Testing/JavaScriptTestEncoder.cs new file mode 100644 index 0000000000..bef4461676 --- /dev/null +++ b/src/WebEncoders/src/Testing/JavaScriptTestEncoder.cs @@ -0,0 +1,104 @@ +// 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.IO; +using System.Text.Encodings.Web; + +namespace Microsoft.Extensions.WebEncoders.Testing +{ + /// + /// Encoder used for unit testing. + /// + public class JavaScriptTestEncoder : JavaScriptEncoder + { + public override int MaxOutputCharactersPerInputCharacter + { + get { return 1; } + } + + public override string Encode(string value) + { + if (value == null) + { + throw new ArgumentNullException(nameof(value)); + } + + if (value.Length == 0) + { + return string.Empty; + } + + return $"JavaScriptEncode[[{value}]]"; + } + + public override void Encode(TextWriter output, char[] value, int startIndex, int characterCount) + { + if (output == null) + { + throw new ArgumentNullException(nameof(output)); + } + + if (value == null) + { + throw new ArgumentNullException(nameof(value)); + } + + if (characterCount == 0) + { + return; + } + + output.Write("JavaScriptEncode[["); + output.Write(value, startIndex, characterCount); + output.Write("]]"); + } + + public override void Encode(TextWriter output, string value, int startIndex, int characterCount) + { + if (output == null) + { + throw new ArgumentNullException(nameof(output)); + } + + if (value == null) + { + throw new ArgumentNullException(nameof(value)); + } + + if (characterCount == 0) + { + return; + } + + output.Write("JavaScriptEncode[["); + output.Write(value.Substring(startIndex, characterCount)); + output.Write("]]"); + } + + public override bool WillEncode(int unicodeScalar) + { + return false; + } + + public override unsafe int FindFirstCharacterToEncode(char* text, int textLength) + { + return -1; + } + + public override unsafe bool TryEncodeUnicodeScalar( + int unicodeScalar, + char* buffer, + int bufferLength, + out int numberOfCharactersWritten) + { + if (buffer == null) + { + throw new ArgumentNullException(nameof(buffer)); + } + + numberOfCharactersWritten = 0; + return false; + } + } +} \ No newline at end of file diff --git a/src/WebEncoders/src/Testing/UrlTestEncoder.cs b/src/WebEncoders/src/Testing/UrlTestEncoder.cs new file mode 100644 index 0000000000..295bda63e8 --- /dev/null +++ b/src/WebEncoders/src/Testing/UrlTestEncoder.cs @@ -0,0 +1,104 @@ +// 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.IO; +using System.Text.Encodings.Web; + +namespace Microsoft.Extensions.WebEncoders.Testing +{ + /// + /// Encoder used for unit testing. + /// + public class UrlTestEncoder : UrlEncoder + { + public override int MaxOutputCharactersPerInputCharacter + { + get { return 1; } + } + + public override string Encode(string value) + { + if (value == null) + { + throw new ArgumentNullException(nameof(value)); + } + + if (value.Length == 0) + { + return string.Empty; + } + + return $"UrlEncode[[{value}]]"; + } + + public override void Encode(TextWriter output, char[] value, int startIndex, int characterCount) + { + if (output == null) + { + throw new ArgumentNullException(nameof(output)); + } + + if (value == null) + { + throw new ArgumentNullException(nameof(value)); + } + + if (characterCount == 0) + { + return; + } + + output.Write("UrlEncode[["); + output.Write(value, startIndex, characterCount); + output.Write("]]"); + } + + public override void Encode(TextWriter output, string value, int startIndex, int characterCount) + { + if (output == null) + { + throw new ArgumentNullException(nameof(output)); + } + + if (value == null) + { + throw new ArgumentNullException(nameof(value)); + } + + if (characterCount == 0) + { + return; + } + + output.Write("UrlEncode[["); + output.Write(value.Substring(startIndex, characterCount)); + output.Write("]]"); + } + + public override bool WillEncode(int unicodeScalar) + { + return false; + } + + public override unsafe int FindFirstCharacterToEncode(char* text, int textLength) + { + return -1; + } + + public override unsafe bool TryEncodeUnicodeScalar( + int unicodeScalar, + char* buffer, + int bufferLength, + out int numberOfCharactersWritten) + { + if (buffer == null) + { + throw new ArgumentNullException(nameof(buffer)); + } + + numberOfCharactersWritten = 0; + return false; + } + } +} \ No newline at end of file diff --git a/src/WebEncoders/src/WebEncoderOptions.cs b/src/WebEncoders/src/WebEncoderOptions.cs new file mode 100644 index 0000000000..2f5e770a0c --- /dev/null +++ b/src/WebEncoders/src/WebEncoderOptions.cs @@ -0,0 +1,21 @@ +// 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.Text.Encodings.Web; + +namespace Microsoft.Extensions.WebEncoders +{ + /// + /// Specifies options common to all three encoders (HtmlEncode, JavaScriptEncode, UrlEncode). + /// + public sealed class WebEncoderOptions + { + /// + /// Specifies which code points are allowed to be represented unescaped by the encoders. + /// + /// + /// If this property is null, then the encoders will use their default allow lists. + /// + public TextEncoderSettings TextEncoderSettings { get; set; } + } +} diff --git a/src/WebEncoders/src/baseline.netcore.json b/src/WebEncoders/src/baseline.netcore.json new file mode 100644 index 0000000000..6da0ae0754 --- /dev/null +++ b/src/WebEncoders/src/baseline.netcore.json @@ -0,0 +1,564 @@ +{ + "AssemblyIdentity": "Microsoft.Extensions.WebEncoders, Version=2.1.1.0, Culture=neutral, PublicKeyToken=adb9793829ddae60", + "Types": [ + { + "Name": "Microsoft.Extensions.WebEncoders.WebEncoderOptions", + "Visibility": "Public", + "Kind": "Class", + "Sealed": true, + "ImplementedInterfaces": [], + "Members": [ + { + "Kind": "Method", + "Name": "get_TextEncoderSettings", + "Parameters": [], + "ReturnType": "System.Text.Encodings.Web.TextEncoderSettings", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "set_TextEncoderSettings", + "Parameters": [ + { + "Name": "value", + "Type": "System.Text.Encodings.Web.TextEncoderSettings" + } + ], + "ReturnType": "System.Void", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Constructor", + "Name": ".ctor", + "Parameters": [], + "Visibility": "Public", + "GenericParameter": [] + } + ], + "GenericParameters": [] + }, + { + "Name": "Microsoft.Extensions.WebEncoders.Testing.HtmlTestEncoder", + "Visibility": "Public", + "Kind": "Class", + "Sealed": true, + "BaseType": "System.Text.Encodings.Web.HtmlEncoder", + "ImplementedInterfaces": [], + "Members": [ + { + "Kind": "Method", + "Name": "get_MaxOutputCharactersPerInputCharacter", + "Parameters": [], + "ReturnType": "System.Int32", + "Virtual": true, + "Override": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "Encode", + "Parameters": [ + { + "Name": "value", + "Type": "System.String" + } + ], + "ReturnType": "System.String", + "Virtual": true, + "Override": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "Encode", + "Parameters": [ + { + "Name": "output", + "Type": "System.IO.TextWriter" + }, + { + "Name": "value", + "Type": "System.Char[]" + }, + { + "Name": "startIndex", + "Type": "System.Int32" + }, + { + "Name": "characterCount", + "Type": "System.Int32" + } + ], + "ReturnType": "System.Void", + "Virtual": true, + "Override": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "Encode", + "Parameters": [ + { + "Name": "output", + "Type": "System.IO.TextWriter" + }, + { + "Name": "value", + "Type": "System.String" + }, + { + "Name": "startIndex", + "Type": "System.Int32" + }, + { + "Name": "characterCount", + "Type": "System.Int32" + } + ], + "ReturnType": "System.Void", + "Virtual": true, + "Override": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "WillEncode", + "Parameters": [ + { + "Name": "unicodeScalar", + "Type": "System.Int32" + } + ], + "ReturnType": "System.Boolean", + "Virtual": true, + "Override": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "FindFirstCharacterToEncode", + "Parameters": [ + { + "Name": "text", + "Type": "System.Char*" + }, + { + "Name": "textLength", + "Type": "System.Int32" + } + ], + "ReturnType": "System.Int32", + "Virtual": true, + "Override": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "TryEncodeUnicodeScalar", + "Parameters": [ + { + "Name": "unicodeScalar", + "Type": "System.Int32" + }, + { + "Name": "buffer", + "Type": "System.Char*" + }, + { + "Name": "bufferLength", + "Type": "System.Int32" + }, + { + "Name": "numberOfCharactersWritten", + "Type": "System.Int32", + "Direction": "Out" + } + ], + "ReturnType": "System.Boolean", + "Virtual": true, + "Override": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Constructor", + "Name": ".ctor", + "Parameters": [], + "Visibility": "Public", + "GenericParameter": [] + } + ], + "GenericParameters": [] + }, + { + "Name": "Microsoft.Extensions.WebEncoders.Testing.JavaScriptTestEncoder", + "Visibility": "Public", + "Kind": "Class", + "BaseType": "System.Text.Encodings.Web.JavaScriptEncoder", + "ImplementedInterfaces": [], + "Members": [ + { + "Kind": "Method", + "Name": "get_MaxOutputCharactersPerInputCharacter", + "Parameters": [], + "ReturnType": "System.Int32", + "Virtual": true, + "Override": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "Encode", + "Parameters": [ + { + "Name": "value", + "Type": "System.String" + } + ], + "ReturnType": "System.String", + "Virtual": true, + "Override": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "Encode", + "Parameters": [ + { + "Name": "output", + "Type": "System.IO.TextWriter" + }, + { + "Name": "value", + "Type": "System.Char[]" + }, + { + "Name": "startIndex", + "Type": "System.Int32" + }, + { + "Name": "characterCount", + "Type": "System.Int32" + } + ], + "ReturnType": "System.Void", + "Virtual": true, + "Override": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "Encode", + "Parameters": [ + { + "Name": "output", + "Type": "System.IO.TextWriter" + }, + { + "Name": "value", + "Type": "System.String" + }, + { + "Name": "startIndex", + "Type": "System.Int32" + }, + { + "Name": "characterCount", + "Type": "System.Int32" + } + ], + "ReturnType": "System.Void", + "Virtual": true, + "Override": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "WillEncode", + "Parameters": [ + { + "Name": "unicodeScalar", + "Type": "System.Int32" + } + ], + "ReturnType": "System.Boolean", + "Virtual": true, + "Override": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "FindFirstCharacterToEncode", + "Parameters": [ + { + "Name": "text", + "Type": "System.Char*" + }, + { + "Name": "textLength", + "Type": "System.Int32" + } + ], + "ReturnType": "System.Int32", + "Virtual": true, + "Override": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "TryEncodeUnicodeScalar", + "Parameters": [ + { + "Name": "unicodeScalar", + "Type": "System.Int32" + }, + { + "Name": "buffer", + "Type": "System.Char*" + }, + { + "Name": "bufferLength", + "Type": "System.Int32" + }, + { + "Name": "numberOfCharactersWritten", + "Type": "System.Int32", + "Direction": "Out" + } + ], + "ReturnType": "System.Boolean", + "Virtual": true, + "Override": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Constructor", + "Name": ".ctor", + "Parameters": [], + "Visibility": "Public", + "GenericParameter": [] + } + ], + "GenericParameters": [] + }, + { + "Name": "Microsoft.Extensions.WebEncoders.Testing.UrlTestEncoder", + "Visibility": "Public", + "Kind": "Class", + "BaseType": "System.Text.Encodings.Web.UrlEncoder", + "ImplementedInterfaces": [], + "Members": [ + { + "Kind": "Method", + "Name": "get_MaxOutputCharactersPerInputCharacter", + "Parameters": [], + "ReturnType": "System.Int32", + "Virtual": true, + "Override": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "Encode", + "Parameters": [ + { + "Name": "value", + "Type": "System.String" + } + ], + "ReturnType": "System.String", + "Virtual": true, + "Override": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "Encode", + "Parameters": [ + { + "Name": "output", + "Type": "System.IO.TextWriter" + }, + { + "Name": "value", + "Type": "System.Char[]" + }, + { + "Name": "startIndex", + "Type": "System.Int32" + }, + { + "Name": "characterCount", + "Type": "System.Int32" + } + ], + "ReturnType": "System.Void", + "Virtual": true, + "Override": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "Encode", + "Parameters": [ + { + "Name": "output", + "Type": "System.IO.TextWriter" + }, + { + "Name": "value", + "Type": "System.String" + }, + { + "Name": "startIndex", + "Type": "System.Int32" + }, + { + "Name": "characterCount", + "Type": "System.Int32" + } + ], + "ReturnType": "System.Void", + "Virtual": true, + "Override": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "WillEncode", + "Parameters": [ + { + "Name": "unicodeScalar", + "Type": "System.Int32" + } + ], + "ReturnType": "System.Boolean", + "Virtual": true, + "Override": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "FindFirstCharacterToEncode", + "Parameters": [ + { + "Name": "text", + "Type": "System.Char*" + }, + { + "Name": "textLength", + "Type": "System.Int32" + } + ], + "ReturnType": "System.Int32", + "Virtual": true, + "Override": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "TryEncodeUnicodeScalar", + "Parameters": [ + { + "Name": "unicodeScalar", + "Type": "System.Int32" + }, + { + "Name": "buffer", + "Type": "System.Char*" + }, + { + "Name": "bufferLength", + "Type": "System.Int32" + }, + { + "Name": "numberOfCharactersWritten", + "Type": "System.Int32", + "Direction": "Out" + } + ], + "ReturnType": "System.Boolean", + "Virtual": true, + "Override": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Constructor", + "Name": ".ctor", + "Parameters": [], + "Visibility": "Public", + "GenericParameter": [] + } + ], + "GenericParameters": [] + }, + { + "Name": "Microsoft.Extensions.DependencyInjection.EncoderServiceCollectionExtensions", + "Visibility": "Public", + "Kind": "Class", + "Abstract": true, + "Static": true, + "Sealed": true, + "ImplementedInterfaces": [], + "Members": [ + { + "Kind": "Method", + "Name": "AddWebEncoders", + "Parameters": [ + { + "Name": "services", + "Type": "Microsoft.Extensions.DependencyInjection.IServiceCollection" + } + ], + "ReturnType": "Microsoft.Extensions.DependencyInjection.IServiceCollection", + "Static": true, + "Extension": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "AddWebEncoders", + "Parameters": [ + { + "Name": "services", + "Type": "Microsoft.Extensions.DependencyInjection.IServiceCollection" + }, + { + "Name": "setupAction", + "Type": "System.Action" + } + ], + "ReturnType": "Microsoft.Extensions.DependencyInjection.IServiceCollection", + "Static": true, + "Extension": true, + "Visibility": "Public", + "GenericParameter": [] + } + ], + "GenericParameters": [] + } + ] +} \ No newline at end of file diff --git a/src/WebEncoders/test/EncoderServiceCollectionExtensionsTests.cs b/src/WebEncoders/test/EncoderServiceCollectionExtensionsTests.cs new file mode 100644 index 0000000000..0178bba2d5 --- /dev/null +++ b/src/WebEncoders/test/EncoderServiceCollectionExtensionsTests.cs @@ -0,0 +1,90 @@ +// 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.Text.Encodings.Web; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.WebEncoders.Testing; +using Xunit; + +namespace Microsoft.Extensions.WebEncoders +{ + public class EncoderServiceCollectionExtensionsTests + { + [Fact] + public void AddWebEncoders_WithoutOptions_RegistersDefaultEncoders() + { + // Arrange + var serviceCollection = new ServiceCollection(); + + // Act + serviceCollection.AddWebEncoders(); + + // Assert + var serviceProvider = serviceCollection.BuildServiceProvider(); + Assert.Same(HtmlEncoder.Default, serviceProvider.GetRequiredService()); // default encoder + Assert.Same(HtmlEncoder.Default, serviceProvider.GetRequiredService()); // as singleton instance + Assert.Same(JavaScriptEncoder.Default, serviceProvider.GetRequiredService()); // default encoder + Assert.Same(JavaScriptEncoder.Default, serviceProvider.GetRequiredService()); // as singleton instance + Assert.Same(UrlEncoder.Default, serviceProvider.GetRequiredService()); // default encoder + Assert.Same(UrlEncoder.Default, serviceProvider.GetRequiredService()); // as singleton instance + } + + [Fact] + public void AddWebEncoders_WithOptions_RegistersEncodersWithCustomCodeFilter() + { + // Arrange + var serviceCollection = new ServiceCollection(); + + // Act + serviceCollection.AddWebEncoders(options => + { + options.TextEncoderSettings = new TextEncoderSettings(); + options.TextEncoderSettings.AllowCharacters("ace".ToCharArray()); // only these three chars are allowed + }); + + // Assert + var serviceProvider = serviceCollection.BuildServiceProvider(); + + var htmlEncoder = serviceProvider.GetRequiredService(); + Assert.Equal("abcde", htmlEncoder.Encode("abcde")); + Assert.Same(htmlEncoder, serviceProvider.GetRequiredService()); // as singleton instance + + var javaScriptEncoder = serviceProvider.GetRequiredService(); + Assert.Equal(@"a\u0062c\u0064e", javaScriptEncoder.Encode("abcde")); + Assert.Same(javaScriptEncoder, serviceProvider.GetRequiredService()); // as singleton instance + + var urlEncoder = serviceProvider.GetRequiredService(); + Assert.Equal("a%62c%64e", urlEncoder.Encode("abcde")); + Assert.Same(urlEncoder, serviceProvider.GetRequiredService()); // as singleton instance + } + + [Fact] + public void AddWebEncoders_DoesNotOverrideExistingRegisteredEncoders() + { + // Arrange + var serviceCollection = new ServiceCollection(); + + // Act + serviceCollection.AddSingleton(); + serviceCollection.AddSingleton(); + // we don't register an existing URL encoder + serviceCollection.AddWebEncoders(options => + { + options.TextEncoderSettings = new TextEncoderSettings(); + options.TextEncoderSettings.AllowCharacters("ace".ToCharArray()); // only these three chars are allowed + }); + + // Assert + var serviceProvider = serviceCollection.BuildServiceProvider(); + + var htmlEncoder = serviceProvider.GetRequiredService(); + Assert.Equal("HtmlEncode[[abcde]]", htmlEncoder.Encode("abcde")); + + var javaScriptEncoder = serviceProvider.GetRequiredService(); + Assert.Equal("JavaScriptEncode[[abcde]]", javaScriptEncoder.Encode("abcde")); + + var urlEncoder = serviceProvider.GetRequiredService(); + Assert.Equal("a%62c%64e", urlEncoder.Encode("abcde")); + } + } +} diff --git a/src/WebEncoders/test/HtmlTestEncoderTest.cs b/src/WebEncoders/test/HtmlTestEncoderTest.cs new file mode 100644 index 0000000000..baafedc4de --- /dev/null +++ b/src/WebEncoders/test/HtmlTestEncoderTest.cs @@ -0,0 +1,26 @@ +// 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 Xunit; + +namespace Microsoft.Extensions.WebEncoders.Testing +{ + public class HtmlTestEncoderTest + { + [Theory] + [InlineData("", "")] + [InlineData("abcd", "HtmlEncode[[abcd]]")] + [InlineData("<<''\"\">>", "HtmlEncode[[<<''\"\">>]]")] + public void StringEncode_EncodesAsExpected(string input, string expectedOutput) + { + // Arrange + var encoder = new HtmlTestEncoder(); + + // Act + var output = encoder.Encode(input); + + // Assert + Assert.Equal(expectedOutput, output); + } + } +} diff --git a/src/WebEncoders/test/Microsoft.Extensions.WebEncoders.Tests.csproj b/src/WebEncoders/test/Microsoft.Extensions.WebEncoders.Tests.csproj new file mode 100755 index 0000000000..729dc5c61a --- /dev/null +++ b/src/WebEncoders/test/Microsoft.Extensions.WebEncoders.Tests.csproj @@ -0,0 +1,12 @@ + + + + $(StandardTestTfms) + + + + + + + + From 83c88bd6bdede632ad38f753bf1e9a30b40ed7dc Mon Sep 17 00:00:00 2001 From: Gert Driesen Date: Tue, 13 Nov 2018 19:03:55 +0100 Subject: [PATCH 0016/1101] Eliminate use of method groups in PropertyHelper (dotnet/extensions#465) * Eliminate use of method groups, and use for loop to enumerate elements of array. Improves performance and reduces allocations. * PR feedback: Remove extra parentheses, and undo change from foreach to for loop. * PR feedback Revert change from for to foreach. * var-ify TryGetValue out parameter. \n\nCommit migrated from https://github.com/dotnet/extensions/commit/08adb5c2ca594683a43dfc1e6178fffc5e336456 --- src/Shared/PropertyHelper/PropertyHelper.cs | 22 +++++++++------------ 1 file changed, 9 insertions(+), 13 deletions(-) diff --git a/src/Shared/PropertyHelper/PropertyHelper.cs b/src/Shared/PropertyHelper/PropertyHelper.cs index f6aad151e5..f3641e03dc 100644 --- a/src/Shared/PropertyHelper/PropertyHelper.cs +++ b/src/Shared/PropertyHelper/PropertyHelper.cs @@ -7,7 +7,6 @@ using System.Collections.Generic; using System.Diagnostics; using System.Linq; using System.Reflection; -using System.Runtime.CompilerServices; namespace Microsoft.Extensions.Internal { @@ -145,7 +144,7 @@ namespace Microsoft.Extensions.Internal /// public static PropertyHelper[] GetProperties(Type type) { - return GetProperties(type, CreateInstance, PropertiesCache); + return GetProperties(type, p => CreateInstance(p), PropertiesCache); } /// @@ -164,7 +163,7 @@ namespace Microsoft.Extensions.Internal /// public static PropertyHelper[] GetVisibleProperties(TypeInfo typeInfo) { - return GetVisibleProperties(typeInfo.AsType(), CreateInstance, PropertiesCache, VisiblePropertiesCache); + return GetVisibleProperties(typeInfo.AsType(), p => CreateInstance(p), PropertiesCache, VisiblePropertiesCache); } /// @@ -183,7 +182,7 @@ namespace Microsoft.Extensions.Internal /// public static PropertyHelper[] GetVisibleProperties(Type type) { - return GetVisibleProperties(type, CreateInstance, PropertiesCache, VisiblePropertiesCache); + return GetVisibleProperties(type, p => CreateInstance(p), PropertiesCache, VisiblePropertiesCache); } /// @@ -420,8 +419,7 @@ namespace Microsoft.Extensions.Internal ConcurrentDictionary allPropertiesCache, ConcurrentDictionary visiblePropertiesCache) { - PropertyHelper[] result; - if (visiblePropertiesCache.TryGetValue(type, out result)) + if (visiblePropertiesCache.TryGetValue(type, out var result)) { return result; } @@ -497,18 +495,17 @@ namespace Microsoft.Extensions.Internal // part of the sequence of properties returned by this method. type = Nullable.GetUnderlyingType(type) ?? type; - PropertyHelper[] helpers; - if (!cache.TryGetValue(type, out helpers)) + if (!cache.TryGetValue(type, out var helpers)) { // We avoid loading indexed properties using the Where statement. - var properties = type.GetRuntimeProperties().Where(IsInterestingProperty); + var properties = type.GetRuntimeProperties().Where(p => IsInterestingProperty(p)); var typeInfo = type.GetTypeInfo(); if (typeInfo.IsInterface) { // Reflection does not return information about inherited properties on the interface itself. properties = properties.Concat(typeInfo.ImplementedInterfaces.SelectMany( - interfaceType => interfaceType.GetRuntimeProperties().Where(IsInterestingProperty))); + interfaceType => interfaceType.GetRuntimeProperties().Where(p => IsInterestingProperty(p)))); } helpers = properties.Select(p => createPropertyHelper(p)).ToArray(); @@ -518,17 +515,16 @@ namespace Microsoft.Extensions.Internal return helpers; } - private static bool IsInterestingProperty(PropertyInfo property) { // For improving application startup time, do not use GetIndexParameters() api early in this check as it // creates a copy of parameter array and also we would like to check for the presence of a get method // and short circuit asap. - return + return property.GetMethod != null && property.GetMethod.IsPublic && !property.GetMethod.IsStatic && - + // PropertyHelper can't work with ref structs. !IsRefStructProperty(property) && From ea14c9c86519396d52c51cfb5b8e8aeb7dacb0c1 Mon Sep 17 00:00:00 2001 From: Javier Calvarro Nelson Date: Tue, 13 Nov 2018 10:11:21 -0800 Subject: [PATCH 0017/1101] Change async void tests to async Task (dotnet/extensions#488) \n\nCommit migrated from https://github.com/dotnet/extensions/commit/d2f4c6a2ed30be5fae6ec166bfb7a3073c9f1d17 --- .../Shared.Tests/ObjectMethodExecutorTest.cs | 30 +++++++++---------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/src/Shared/test/Shared.Tests/ObjectMethodExecutorTest.cs b/src/Shared/test/Shared.Tests/ObjectMethodExecutorTest.cs index 1c26ef1de1..fb9f82aad9 100644 --- a/src/Shared/test/Shared.Tests/ObjectMethodExecutorTest.cs +++ b/src/Shared/test/Shared.Tests/ObjectMethodExecutorTest.cs @@ -155,7 +155,7 @@ namespace Microsoft.Extensions.Internal } [Fact] - public async void TargetMethodReturningCustomAwaitableOfReferenceType_CanInvokeViaExecute() + public async Task TargetMethodReturningCustomAwaitableOfReferenceType_CanInvokeViaExecute() { // Arrange var executor = GetExecutorForMethod("CustomAwaitableOfReferenceTypeAsync"); @@ -171,7 +171,7 @@ namespace Microsoft.Extensions.Internal } [Fact] - public async void TargetMethodReturningCustomAwaitableOfValueType_CanInvokeViaExecute() + public async Task TargetMethodReturningCustomAwaitableOfValueType_CanInvokeViaExecute() { // Arrange var executor = GetExecutorForMethod("CustomAwaitableOfValueTypeAsync"); @@ -186,7 +186,7 @@ namespace Microsoft.Extensions.Internal } [Fact] - public async void TargetMethodReturningCustomAwaitableOfReferenceType_CanInvokeViaExecuteAsync() + public async Task TargetMethodReturningCustomAwaitableOfReferenceType_CanInvokeViaExecuteAsync() { // Arrange var executor = GetExecutorForMethod("CustomAwaitableOfReferenceTypeAsync"); @@ -203,7 +203,7 @@ namespace Microsoft.Extensions.Internal } [Fact] - public async void TargetMethodReturningCustomAwaitableOfValueType_CanInvokeViaExecuteAsync() + public async Task TargetMethodReturningCustomAwaitableOfValueType_CanInvokeViaExecuteAsync() { // Arrange var executor = GetExecutorForMethod("CustomAwaitableOfValueTypeAsync"); @@ -220,7 +220,7 @@ namespace Microsoft.Extensions.Internal } [Fact] - public async void TargetMethodReturningAwaitableOfVoidType_CanInvokeViaExecuteAsync() + public async Task TargetMethodReturningAwaitableOfVoidType_CanInvokeViaExecuteAsync() { // Arrange var executor = GetExecutorForMethod("VoidValueMethodAsync"); @@ -235,7 +235,7 @@ namespace Microsoft.Extensions.Internal } [Fact] - public async void TargetMethodReturningAwaitableWithICriticalNotifyCompletion_UsesUnsafeOnCompleted() + public async Task TargetMethodReturningAwaitableWithICriticalNotifyCompletion_UsesUnsafeOnCompleted() { // Arrange var executor = GetExecutorForMethod("CustomAwaitableWithICriticalNotifyCompletion"); @@ -250,7 +250,7 @@ namespace Microsoft.Extensions.Internal } [Fact] - public async void TargetMethodReturningAwaitableWithoutICriticalNotifyCompletion_UsesOnCompleted() + public async Task TargetMethodReturningAwaitableWithoutICriticalNotifyCompletion_UsesOnCompleted() { // Arrange var executor = GetExecutorForMethod("CustomAwaitableWithoutICriticalNotifyCompletion"); @@ -265,7 +265,7 @@ namespace Microsoft.Extensions.Internal } [Fact] - public async void TargetMethodReturningValueTaskOfValueType_CanBeInvokedViaExecute() + public async Task TargetMethodReturningValueTaskOfValueType_CanBeInvokedViaExecute() { // Arrange var executor = GetExecutorForMethod("ValueTaskOfValueType"); @@ -280,7 +280,7 @@ namespace Microsoft.Extensions.Internal } [Fact] - public async void TargetMethodReturningValueTaskOfReferenceType_CanBeInvokedViaExecute() + public async Task TargetMethodReturningValueTaskOfReferenceType_CanBeInvokedViaExecute() { // Arrange var executor = GetExecutorForMethod("ValueTaskOfReferenceType"); @@ -295,7 +295,7 @@ namespace Microsoft.Extensions.Internal } [Fact] - public async void TargetMethodReturningValueTaskOfValueType_CanBeInvokedViaExecuteAsync() + public async Task TargetMethodReturningValueTaskOfValueType_CanBeInvokedViaExecuteAsync() { // Arrange var executor = GetExecutorForMethod("ValueTaskOfValueType"); @@ -311,7 +311,7 @@ namespace Microsoft.Extensions.Internal } [Fact] - public async void TargetMethodReturningValueTaskOfReferenceType_CanBeInvokedViaExecuteAsync() + public async Task TargetMethodReturningValueTaskOfReferenceType_CanBeInvokedViaExecuteAsync() { // Arrange var executor = GetExecutorForMethod("ValueTaskOfReferenceType"); @@ -326,7 +326,7 @@ namespace Microsoft.Extensions.Internal } [Fact] - public async void TargetMethodReturningFSharpAsync_CanBeInvokedViaExecute() + public async Task TargetMethodReturningFSharpAsync_CanBeInvokedViaExecute() { // Arrange var executor = GetExecutorForMethod("FSharpAsyncMethod"); @@ -344,7 +344,7 @@ namespace Microsoft.Extensions.Internal } [Fact] - public async void TargetMethodReturningFailingFSharpAsync_CanBeInvokedViaExecute() + public async Task TargetMethodReturningFailingFSharpAsync_CanBeInvokedViaExecute() { // Arrange var executor = GetExecutorForMethod("FSharpAsyncFailureMethod"); @@ -365,7 +365,7 @@ namespace Microsoft.Extensions.Internal } [Fact] - public async void TargetMethodReturningFSharpAsync_CanBeInvokedViaExecuteAsync() + public async Task TargetMethodReturningFSharpAsync_CanBeInvokedViaExecuteAsync() { // Arrange var executor = GetExecutorForMethod("FSharpAsyncMethod"); @@ -380,7 +380,7 @@ namespace Microsoft.Extensions.Internal } [Fact] - public async void TargetMethodReturningFailingFSharpAsync_CanBeInvokedViaExecuteAsync() + public async Task TargetMethodReturningFailingFSharpAsync_CanBeInvokedViaExecuteAsync() { // Arrange var executor = GetExecutorForMethod("FSharpAsyncFailureMethod"); From 88003f2c215bff9ca9cf3039f14de45d14ccf7fc Mon Sep 17 00:00:00 2001 From: Nate McMaster Date: Wed, 14 Nov 2018 08:43:07 -0800 Subject: [PATCH 0018/1101] Prepare repo to build 2.2.1 * Update dependencies to 2.2.0 rtm * Update branding to 2.2.1 * Update package baselines to 2.2.0 * Add a restore source for 2.2.0 RTM \n\nCommit migrated from https://github.com/dotnet/extensions/commit/913f74d590bcbdd1165d8faa1d99f3f11197aa4d --- src/Testing/src/Microsoft.AspNetCore.Testing.csproj | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/Testing/src/Microsoft.AspNetCore.Testing.csproj b/src/Testing/src/Microsoft.AspNetCore.Testing.csproj index d9d9008dd2..64e0b3c4e1 100644 --- a/src/Testing/src/Microsoft.AspNetCore.Testing.csproj +++ b/src/Testing/src/Microsoft.AspNetCore.Testing.csproj @@ -8,6 +8,8 @@ aspnetcore false true + + true From 6c5d291b10806decdd8d70d8648012471cda47ca Mon Sep 17 00:00:00 2001 From: Pavel Krymets Date: Fri, 16 Nov 2018 08:33:57 -0800 Subject: [PATCH 0019/1101] Remove internal types from Logging and Abstractions (dotnet/extensions#513) \n\nCommit migrated from https://github.com/dotnet/extensions/commit/55518d79834d3319c91f40b449d028338b129ed6 --- src/Shared/BenchmarkRunner/DefaultCoreValidationConfig.cs | 5 ----- 1 file changed, 5 deletions(-) diff --git a/src/Shared/BenchmarkRunner/DefaultCoreValidationConfig.cs b/src/Shared/BenchmarkRunner/DefaultCoreValidationConfig.cs index 95fc725564..5a90929cff 100644 --- a/src/Shared/BenchmarkRunner/DefaultCoreValidationConfig.cs +++ b/src/Shared/BenchmarkRunner/DefaultCoreValidationConfig.cs @@ -1,11 +1,6 @@ // 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.Linq; -using System.Reflection; -using BenchmarkDotNet.Attributes; -using BenchmarkDotNet.Running; using BenchmarkDotNet.Configs; using BenchmarkDotNet.Jobs; using BenchmarkDotNet.Loggers; From ebb1165b14b0d8f0a6151d8d323f231529e6f67e Mon Sep 17 00:00:00 2001 From: Pavel Krymets Date: Fri, 16 Nov 2018 13:57:43 -0800 Subject: [PATCH 0020/1101] Handle nullable enum default values (dotnet/extensions#531) \n\nCommit migrated from https://github.com/dotnet/extensions/commit/33f839d585ae7664195008d8c9c5e317d5897510 --- .../ParameterDefaultValue.cs | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/src/Shared/ParameterDefaultValue/ParameterDefaultValue.cs b/src/Shared/ParameterDefaultValue/ParameterDefaultValue.cs index a71bad37b1..dc635bb789 100644 --- a/src/Shared/ParameterDefaultValue/ParameterDefaultValue.cs +++ b/src/Shared/ParameterDefaultValue/ParameterDefaultValue.cs @@ -8,6 +8,8 @@ namespace Microsoft.Extensions.Internal { internal class ParameterDefaultValue { + private static readonly Type _nullable = typeof(Nullable<>); + public static bool TryGetDefaultValue(ParameterInfo parameter, out object defaultValue) { bool hasDefaultValue; @@ -39,6 +41,19 @@ namespace Microsoft.Extensions.Internal { defaultValue = Activator.CreateInstance(parameter.ParameterType); } + + // Handle nullable enums + if (defaultValue != null && + parameter.ParameterType.IsGenericType && + parameter.ParameterType.GetGenericTypeDefinition() == _nullable + ) + { + var underlyingType = Nullable.GetUnderlyingType(parameter.ParameterType); + if (underlyingType != null && underlyingType.IsEnum) + { + defaultValue = Enum.ToObject(underlyingType, defaultValue); + } + } } return hasDefaultValue; From 6dc295b5782302ded80a5ae088d99d5868229818 Mon Sep 17 00:00:00 2001 From: Nate McMaster Date: Tue, 20 Nov 2018 20:49:10 -0800 Subject: [PATCH 0021/1101] Reorganize source code in preparation to move into aspnet/Extensions Prior to reorganization, this source code was found in https://github.com/aspnet/Diagnostics/tree/dotnet/extensions@c802d5ef5fba1ba8dfbcb8c3741af2ba15e9d1aa \n\nCommit migrated from https://github.com/dotnet/extensions/commit/a03861270c35c4c7f44c0914c01cdf24602973b2 --- .../Abstractions/src/HealthCheckContext.cs | 13 + .../src/HealthCheckRegistration.cs | 132 +++++ .../Abstractions/src/HealthCheckResult.cs | 88 +++ .../Abstractions/src/HealthReport.cs | 68 +++ .../Abstractions/src/HealthReportEntry.cs | 59 ++ .../Abstractions/src/HealthStatus.cs | 37 ++ .../Abstractions/src/IHealthCheck.cs | 23 + .../Abstractions/src/IHealthCheckPublisher.cs | 39 ++ ...agnostics.HealthChecks.Abstractions.csproj | 16 + .../Abstractions/src/baseline.netcore.json | 5 + .../src/DefaultHealthCheckService.cs | 304 ++++++++++ .../HealthChecks/src/DelegateHealthCheck.cs | 35 ++ .../HealthCheckServiceCollectionExtensions.cs | 33 ++ .../HealthChecksBuilder.cs | 33 ++ .../HealthChecksBuilderAddCheckExtensions.cs | 191 +++++++ .../HealthChecksBuilderDelegateExtensions.cs | 149 +++++ .../IHealthChecksBuilder.cs | 24 + .../HealthChecks/src/HealthCheckLogScope.cs | 48 ++ .../src/HealthCheckPublisherHostedService.cs | 262 +++++++++ .../src/HealthCheckPublisherOptions.cs | 84 +++ .../HealthChecks/src/HealthCheckService.cs | 61 ++ .../src/HealthCheckServiceOptions.cs | 18 + ...Extensions.Diagnostics.HealthChecks.csproj | 26 + .../src/Properties/AssemblyInfo.cs | 3 + .../HealthChecks/src/baseline.netcore.json | 5 + .../test/DefaultHealthCheckServiceTest.cs | 419 ++++++++++++++ .../HealthChecksBuilderTest.cs | 257 +++++++++ .../ServiceCollectionExtensionsTest.cs | 43 ++ .../HealthCheckPublisherHostedServiceTest.cs | 528 ++++++++++++++++++ .../HealthChecks/test/HealthReportTest.cs | 45 ++ ...ions.Diagnostics.HealthChecks.Tests.csproj | 12 + 31 files changed, 3060 insertions(+) create mode 100644 src/HealthChecks/Abstractions/src/HealthCheckContext.cs create mode 100644 src/HealthChecks/Abstractions/src/HealthCheckRegistration.cs create mode 100644 src/HealthChecks/Abstractions/src/HealthCheckResult.cs create mode 100644 src/HealthChecks/Abstractions/src/HealthReport.cs create mode 100644 src/HealthChecks/Abstractions/src/HealthReportEntry.cs create mode 100644 src/HealthChecks/Abstractions/src/HealthStatus.cs create mode 100644 src/HealthChecks/Abstractions/src/IHealthCheck.cs create mode 100644 src/HealthChecks/Abstractions/src/IHealthCheckPublisher.cs create mode 100644 src/HealthChecks/Abstractions/src/Microsoft.Extensions.Diagnostics.HealthChecks.Abstractions.csproj create mode 100644 src/HealthChecks/Abstractions/src/baseline.netcore.json create mode 100644 src/HealthChecks/HealthChecks/src/DefaultHealthCheckService.cs create mode 100644 src/HealthChecks/HealthChecks/src/DelegateHealthCheck.cs create mode 100644 src/HealthChecks/HealthChecks/src/DependencyInjection/HealthCheckServiceCollectionExtensions.cs create mode 100644 src/HealthChecks/HealthChecks/src/DependencyInjection/HealthChecksBuilder.cs create mode 100644 src/HealthChecks/HealthChecks/src/DependencyInjection/HealthChecksBuilderAddCheckExtensions.cs create mode 100644 src/HealthChecks/HealthChecks/src/DependencyInjection/HealthChecksBuilderDelegateExtensions.cs create mode 100644 src/HealthChecks/HealthChecks/src/DependencyInjection/IHealthChecksBuilder.cs create mode 100644 src/HealthChecks/HealthChecks/src/HealthCheckLogScope.cs create mode 100644 src/HealthChecks/HealthChecks/src/HealthCheckPublisherHostedService.cs create mode 100644 src/HealthChecks/HealthChecks/src/HealthCheckPublisherOptions.cs create mode 100644 src/HealthChecks/HealthChecks/src/HealthCheckService.cs create mode 100644 src/HealthChecks/HealthChecks/src/HealthCheckServiceOptions.cs create mode 100644 src/HealthChecks/HealthChecks/src/Microsoft.Extensions.Diagnostics.HealthChecks.csproj create mode 100644 src/HealthChecks/HealthChecks/src/Properties/AssemblyInfo.cs create mode 100644 src/HealthChecks/HealthChecks/src/baseline.netcore.json create mode 100644 src/HealthChecks/HealthChecks/test/DefaultHealthCheckServiceTest.cs create mode 100644 src/HealthChecks/HealthChecks/test/DependencyInjection/HealthChecksBuilderTest.cs create mode 100644 src/HealthChecks/HealthChecks/test/DependencyInjection/ServiceCollectionExtensionsTest.cs create mode 100644 src/HealthChecks/HealthChecks/test/HealthCheckPublisherHostedServiceTest.cs create mode 100644 src/HealthChecks/HealthChecks/test/HealthReportTest.cs create mode 100644 src/HealthChecks/HealthChecks/test/Microsoft.Extensions.Diagnostics.HealthChecks.Tests.csproj diff --git a/src/HealthChecks/Abstractions/src/HealthCheckContext.cs b/src/HealthChecks/Abstractions/src/HealthCheckContext.cs new file mode 100644 index 0000000000..027451c0d2 --- /dev/null +++ b/src/HealthChecks/Abstractions/src/HealthCheckContext.cs @@ -0,0 +1,13 @@ +// 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. + +namespace Microsoft.Extensions.Diagnostics.HealthChecks +{ + public sealed class HealthCheckContext + { + /// + /// Gets or sets the of the currently executing . + /// + public HealthCheckRegistration Registration { get; set; } + } +} diff --git a/src/HealthChecks/Abstractions/src/HealthCheckRegistration.cs b/src/HealthChecks/Abstractions/src/HealthCheckRegistration.cs new file mode 100644 index 0000000000..9291c38846 --- /dev/null +++ b/src/HealthChecks/Abstractions/src/HealthCheckRegistration.cs @@ -0,0 +1,132 @@ +// 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.Collections.Generic; + +namespace Microsoft.Extensions.Diagnostics.HealthChecks +{ + /// + /// Represent the registration information associated with an implementation. + /// + /// + /// + /// The health check registration is provided as a separate object so that application developers can customize + /// how health check implementations are configured. + /// + /// + /// The registration is provided to an implementation during execution through + /// . This allows a health check implementation to access named + /// options or perform other operations based on the registered name. + /// + /// + public sealed class HealthCheckRegistration + { + private Func _factory; + private string _name; + + /// + /// Creates a new for an existing instance. + /// + /// The health check name. + /// The instance. + /// + /// The that should be reported upon failure of the health check. If the provided value + /// is null, then will be reported. + /// + /// A list of tags that can be used for filtering health checks. + public HealthCheckRegistration(string name, IHealthCheck instance, HealthStatus? failureStatus, IEnumerable tags) + { + if (name == null) + { + throw new ArgumentNullException(nameof(name)); + } + + if (instance == null) + { + throw new ArgumentNullException(nameof(instance)); + } + + Name = name; + FailureStatus = failureStatus ?? HealthStatus.Unhealthy; + Tags = new HashSet(tags ?? Array.Empty(), StringComparer.OrdinalIgnoreCase); + Factory = (_) => instance; + } + + /// + /// Creates a new for an existing instance. + /// + /// The health check name. + /// A delegate used to create the instance. + /// + /// The that should be reported when the health check reports a failure. If the provided value + /// is null, then will be reported. + /// + /// A list of tags that can be used for filtering health checks. + public HealthCheckRegistration( + string name, + Func factory, + HealthStatus? failureStatus, + IEnumerable tags) + { + if (name == null) + { + throw new ArgumentNullException(nameof(name)); + } + + if (factory == null) + { + throw new ArgumentNullException(nameof(factory)); + } + + Name = name; + FailureStatus = failureStatus ?? HealthStatus.Unhealthy; + Tags = new HashSet(tags ?? Array.Empty(), StringComparer.OrdinalIgnoreCase); + Factory = factory; + } + + /// + /// Gets or sets a delegate used to create the instance. + /// + public Func Factory + { + get => _factory; + set + { + if (value == null) + { + throw new ArgumentNullException(nameof(value)); + } + + _factory = value; + } + } + + /// + /// Gets or sets the that should be reported upon failure of the health check. + /// + public HealthStatus FailureStatus { get; set; } + + /// + /// Gets or sets the health check name. + /// + public string Name + { + get => _name; + set + { + if (value == null) + { + throw new ArgumentNullException(nameof(value)); + } + + _name = value; + } + } + + /// + /// Gets a list of tags that can be used for filtering health checks. + /// + public ISet Tags { get; } + } +} diff --git a/src/HealthChecks/Abstractions/src/HealthCheckResult.cs b/src/HealthChecks/Abstractions/src/HealthCheckResult.cs new file mode 100644 index 0000000000..e01cb5aceb --- /dev/null +++ b/src/HealthChecks/Abstractions/src/HealthCheckResult.cs @@ -0,0 +1,88 @@ +// 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.Collections.Generic; + +namespace Microsoft.Extensions.Diagnostics.HealthChecks +{ + /// + /// Represents the result of a health check. + /// + public struct HealthCheckResult + { + private static readonly IReadOnlyDictionary _emptyReadOnlyDictionary = new Dictionary(); + + /// + /// Creates a new with the specified values for , + /// , , and . + /// + /// A value indicating the status of the component that was checked. + /// A human-readable description of the status of the component that was checked. + /// An representing the exception that was thrown when checking for status (if any). + /// Additional key-value pairs describing the health of the component. + public HealthCheckResult(HealthStatus status, string description = null, Exception exception = null, IReadOnlyDictionary data = null) + { + Status = status; + Description = description; + Exception = exception; + Data = data ?? _emptyReadOnlyDictionary; + } + + /// + /// Gets additional key-value pairs describing the health of the component. + /// + public IReadOnlyDictionary Data { get; } + + /// + /// Gets a human-readable description of the status of the component that was checked. + /// + public string Description { get; } + + /// + /// Gets an representing the exception that was thrown when checking for status (if any). + /// + public Exception Exception { get; } + + /// + /// Gets a value indicating the status of the component that was checked. + /// + public HealthStatus Status { get; } + + /// + /// Creates a representing a healthy component. + /// + /// A human-readable description of the status of the component that was checked. Optional. + /// Additional key-value pairs describing the health of the component. Optional. + /// A representing a healthy component. + public static HealthCheckResult Healthy(string description = null, IReadOnlyDictionary data = null) + { + return new HealthCheckResult(status: HealthStatus.Healthy, description, exception: null, data); + } + + + /// + /// Creates a representing a degraded component. + /// + /// A human-readable description of the status of the component that was checked. Optional. + /// An representing the exception that was thrown when checking for status. Optional. + /// Additional key-value pairs describing the health of the component. Optional. + /// A representing a degraged component. + public static HealthCheckResult Degraded(string description = null, Exception exception = null, IReadOnlyDictionary data = null) + { + return new HealthCheckResult(status: HealthStatus.Degraded, description, exception: null, data); + } + + /// + /// Creates a representing an unhealthy component. + /// + /// A human-readable description of the status of the component that was checked. Optional. + /// An representing the exception that was thrown when checking for status. Optional. + /// Additional key-value pairs describing the health of the component. Optional. + /// A representing an unhealthy component. + public static HealthCheckResult Unhealthy(string description = null, Exception exception = null, IReadOnlyDictionary data = null) + { + return new HealthCheckResult(status: HealthStatus.Unhealthy, description, exception, data); + } + } +} diff --git a/src/HealthChecks/Abstractions/src/HealthReport.cs b/src/HealthChecks/Abstractions/src/HealthReport.cs new file mode 100644 index 0000000000..91ed798811 --- /dev/null +++ b/src/HealthChecks/Abstractions/src/HealthReport.cs @@ -0,0 +1,68 @@ +// 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.Collections.Generic; + +namespace Microsoft.Extensions.Diagnostics.HealthChecks +{ + /// + /// Represents the result of executing a group of instances. + /// + public sealed class HealthReport + { + /// + /// Create a new from the specified results. + /// + /// A containing the results from each health check. + /// A value indicating the time the health check service took to execute. + public HealthReport(IReadOnlyDictionary entries, TimeSpan totalDuration) + { + Entries = entries; + Status = CalculateAggregateStatus(entries.Values); + TotalDuration = totalDuration; + } + + /// + /// A containing the results from each health check. + /// + /// + /// The keys in this dictionary map the name of each executed health check to a for the + /// result data retruned from the corresponding health check. + /// + public IReadOnlyDictionary Entries { get; } + + /// + /// Gets a representing the aggregate status of all the health checks. The value of + /// will be the most servere status reported by a health check. If no checks were executed, the value is always . + /// + public HealthStatus Status { get; } + + /// + /// Gets the time the health check service took to execute. + /// + public TimeSpan TotalDuration { get; } + + private HealthStatus CalculateAggregateStatus(IEnumerable entries) + { + // This is basically a Min() check, but we know the possible range, so we don't need to walk the whole list + var currentValue = HealthStatus.Healthy; + foreach (var entry in entries) + { + if (currentValue > entry.Status) + { + currentValue = entry.Status; + } + + if (currentValue == HealthStatus.Unhealthy) + { + // Game over, man! Game over! + // (We hit the worst possible status, so there's no need to keep iterating) + return currentValue; + } + } + + return currentValue; + } + } +} diff --git a/src/HealthChecks/Abstractions/src/HealthReportEntry.cs b/src/HealthChecks/Abstractions/src/HealthReportEntry.cs new file mode 100644 index 0000000000..6e7d6c6b8e --- /dev/null +++ b/src/HealthChecks/Abstractions/src/HealthReportEntry.cs @@ -0,0 +1,59 @@ +// 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.Collections.Generic; + +namespace Microsoft.Extensions.Diagnostics.HealthChecks +{ + /// + /// Represents an entry in a . Corresponds to the result of a single . + /// + public struct HealthReportEntry + { + private static readonly IReadOnlyDictionary _emptyReadOnlyDictionary = new Dictionary(); + + /// + /// Creates a new with the specified values for , , + /// , and . + /// + /// A value indicating the health status of the component that was checked. + /// A human-readable description of the status of the component that was checked. + /// A value indicating the health execution duration. + /// An representing the exception that was thrown when checking for status (if any). + /// Additional key-value pairs describing the health of the component. + public HealthReportEntry(HealthStatus status, string description, TimeSpan duration, Exception exception, IReadOnlyDictionary data) + { + Status = status; + Description = description; + Duration = duration; + Exception = exception; + Data = data ?? _emptyReadOnlyDictionary; + } + + /// + /// Gets additional key-value pairs describing the health of the component. + /// + public IReadOnlyDictionary Data { get; } + + /// + /// Gets a human-readable description of the status of the component that was checked. + /// + public string Description { get; } + + /// + /// Gets the health check execution duration. + /// + public TimeSpan Duration { get; } + + /// + /// Gets an representing the exception that was thrown when checking for status (if any). + /// + public Exception Exception { get; } + + /// + /// Gets the health status of the component that was checked. + /// + public HealthStatus Status { get; } + } +} diff --git a/src/HealthChecks/Abstractions/src/HealthStatus.cs b/src/HealthChecks/Abstractions/src/HealthStatus.cs new file mode 100644 index 0000000000..61b76d54fa --- /dev/null +++ b/src/HealthChecks/Abstractions/src/HealthStatus.cs @@ -0,0 +1,37 @@ +// 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. + +namespace Microsoft.Extensions.Diagnostics.HealthChecks +{ + /// + /// Represents the reported status of a health check result. + /// + /// + /// + /// A status of should be considered the default value for a failing health check. Application + /// developers may configure a health check to report a different status as desired. + /// + /// + /// The values of this enum or ordered from least healthy to most healthy. So is + /// greater than but less than . + /// + /// + public enum HealthStatus + { + /// + /// Indicates that the health check determined that the component was unhealthy, or an unhandled + /// exception was thrown while executing the health check. + /// + Unhealthy = 0, + + /// + /// Indicates that the health check determined that the component was in a degraded state. + /// + Degraded = 1, + + /// + /// Indicates that the health check determined that the component was healthy. + /// + Healthy = 2, + } +} diff --git a/src/HealthChecks/Abstractions/src/IHealthCheck.cs b/src/HealthChecks/Abstractions/src/IHealthCheck.cs new file mode 100644 index 0000000000..1b69953b67 --- /dev/null +++ b/src/HealthChecks/Abstractions/src/IHealthCheck.cs @@ -0,0 +1,23 @@ +// 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.Threading; +using System.Threading.Tasks; + +namespace Microsoft.Extensions.Diagnostics.HealthChecks +{ + /// + /// Represents a health check, which can be used to check the status of a component in the application, such as a backend service, database or some internal + /// state. + /// + public interface IHealthCheck + { + /// + /// Runs the health check, returning the status of the component being checked. + /// + /// A context object associated with the current execution. + /// A that can be used to cancel the health check. + /// A that completes when the health check has finished, yielding the status of the component being checked. + Task CheckHealthAsync(HealthCheckContext context, CancellationToken cancellationToken = default); + } +} diff --git a/src/HealthChecks/Abstractions/src/IHealthCheckPublisher.cs b/src/HealthChecks/Abstractions/src/IHealthCheckPublisher.cs new file mode 100644 index 0000000000..f1809c4bb8 --- /dev/null +++ b/src/HealthChecks/Abstractions/src/IHealthCheckPublisher.cs @@ -0,0 +1,39 @@ +// 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.Threading; +using System.Threading.Tasks; + +namespace Microsoft.Extensions.Diagnostics.HealthChecks +{ + /// + /// Represents a publisher of information. + /// + /// + /// + /// The default health checks implementation provided an IHostedService implementation that can + /// be used to execute health checks at regular intervals and provide the resulting + /// data to all registered instances. + /// + /// + /// To provide an implementation, register an instance or type as a singleton + /// service in the dependency injection container. + /// + /// + /// instances are provided with a after executing + /// health checks in a background thread. The use of depend on hosting in + /// an application using IWebHost or generic host (IHost). Execution of + /// instance is not related to execution of health checks via a middleware. + /// + /// + public interface IHealthCheckPublisher + { + /// + /// Publishes the provided . + /// + /// The . The result of executing a set of health checks. + /// The . + /// A which will complete when publishing is complete. + Task PublishAsync(HealthReport report, CancellationToken cancellationToken); + } +} diff --git a/src/HealthChecks/Abstractions/src/Microsoft.Extensions.Diagnostics.HealthChecks.Abstractions.csproj b/src/HealthChecks/Abstractions/src/Microsoft.Extensions.Diagnostics.HealthChecks.Abstractions.csproj new file mode 100644 index 0000000000..b95d66f7b3 --- /dev/null +++ b/src/HealthChecks/Abstractions/src/Microsoft.Extensions.Diagnostics.HealthChecks.Abstractions.csproj @@ -0,0 +1,16 @@ + + + + Abstractions for defining health checks in .NET applications + +Commonly Used Types +Microsoft.Extensions.Diagnostics.HealthChecks.IHealthCheck + + Microsoft.Extensions.Diagnostics.HealthChecks + netstandard2.0 + $(NoWarn);CS1591 + true + diagnostics;healthchecks + + + diff --git a/src/HealthChecks/Abstractions/src/baseline.netcore.json b/src/HealthChecks/Abstractions/src/baseline.netcore.json new file mode 100644 index 0000000000..871db4c089 --- /dev/null +++ b/src/HealthChecks/Abstractions/src/baseline.netcore.json @@ -0,0 +1,5 @@ +{ + "AssemblyIdentity": "Microsoft.Extensions.Diagnostics.HealthChecks.Abstractions, Version=2.1.1.0, Culture=neutral, PublicKeyToken=adb9793829ddae60", + "Types": [ + ] +} \ No newline at end of file diff --git a/src/HealthChecks/HealthChecks/src/DefaultHealthCheckService.cs b/src/HealthChecks/HealthChecks/src/DefaultHealthCheckService.cs new file mode 100644 index 0000000000..d5d71d9cb4 --- /dev/null +++ b/src/HealthChecks/HealthChecks/src/DefaultHealthCheckService.cs @@ -0,0 +1,304 @@ +// 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.Collections; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Internal; +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Options; + +namespace Microsoft.Extensions.Diagnostics.HealthChecks +{ + internal class DefaultHealthCheckService : HealthCheckService + { + private readonly IServiceScopeFactory _scopeFactory; + private readonly IOptions _options; + private readonly ILogger _logger; + + public DefaultHealthCheckService( + IServiceScopeFactory scopeFactory, + IOptions options, + ILogger logger) + { + _scopeFactory = scopeFactory ?? throw new ArgumentNullException(nameof(scopeFactory)); + _options = options ?? throw new ArgumentNullException(nameof(options)); + _logger = logger ?? throw new ArgumentNullException(nameof(logger)); + + // We're specifically going out of our way to do this at startup time. We want to make sure you + // get any kind of health-check related error as early as possible. Waiting until someone + // actually tries to **run** health checks would be real baaaaad. + ValidateRegistrations(_options.Value.Registrations); + } + public override async Task CheckHealthAsync( + Func predicate, + CancellationToken cancellationToken = default) + { + var registrations = _options.Value.Registrations; + + using (var scope = _scopeFactory.CreateScope()) + { + var context = new HealthCheckContext(); + var entries = new Dictionary(StringComparer.OrdinalIgnoreCase); + + var totalTime = ValueStopwatch.StartNew(); + Log.HealthCheckProcessingBegin(_logger); + + foreach (var registration in registrations) + { + if (predicate != null && !predicate(registration)) + { + continue; + } + + cancellationToken.ThrowIfCancellationRequested(); + + var healthCheck = registration.Factory(scope.ServiceProvider); + + // If the health check does things like make Database queries using EF or backend HTTP calls, + // it may be valuable to know that logs it generates are part of a health check. So we start a scope. + using (_logger.BeginScope(new HealthCheckLogScope(registration.Name))) + { + var stopwatch = ValueStopwatch.StartNew(); + context.Registration = registration; + + Log.HealthCheckBegin(_logger, registration); + + HealthReportEntry entry; + try + { + var result = await healthCheck.CheckHealthAsync(context, cancellationToken); + var duration = stopwatch.GetElapsedTime(); + + entry = new HealthReportEntry( + status: result.Status, + description: result.Description, + duration: duration, + exception: result.Exception, + data: result.Data); + + Log.HealthCheckEnd(_logger, registration, entry, duration); + Log.HealthCheckData(_logger, registration, entry); + } + + // Allow cancellation to propagate. + catch (Exception ex) when (ex as OperationCanceledException == null) + { + var duration = stopwatch.GetElapsedTime(); + entry = new HealthReportEntry( + status: HealthStatus.Unhealthy, + description: ex.Message, + duration: duration, + exception: ex, + data: null); + + Log.HealthCheckError(_logger, registration, ex, duration); + } + + entries[registration.Name] = entry; + } + } + + var totalElapsedTime = totalTime.GetElapsedTime(); + var report = new HealthReport(entries, totalElapsedTime); + Log.HealthCheckProcessingEnd(_logger, report.Status, totalElapsedTime); + return report; + } + } + + private static void ValidateRegistrations(IEnumerable registrations) + { + // Scan the list for duplicate names to provide a better error if there are duplicates. + var duplicateNames = registrations + .GroupBy(c => c.Name, StringComparer.OrdinalIgnoreCase) + .Where(g => g.Count() > 1) + .Select(g => g.Key) + .ToList(); + + if (duplicateNames.Count > 0) + { + throw new ArgumentException($"Duplicate health checks were registered with the name(s): {string.Join(", ", duplicateNames)}", nameof(registrations)); + } + } + + internal static class EventIds + { + public static readonly EventId HealthCheckProcessingBegin = new EventId(100, "HealthCheckProcessingBegin"); + public static readonly EventId HealthCheckProcessingEnd = new EventId(101, "HealthCheckProcessingEnd"); + + public static readonly EventId HealthCheckBegin = new EventId(102, "HealthCheckBegin"); + public static readonly EventId HealthCheckEnd = new EventId(103, "HealthCheckEnd"); + public static readonly EventId HealthCheckError = new EventId(104, "HealthCheckError"); + public static readonly EventId HealthCheckData = new EventId(105, "HealthCheckData"); + } + + private static class Log + { + private static readonly Action _healthCheckProcessingBegin = LoggerMessage.Define( + LogLevel.Debug, + EventIds.HealthCheckProcessingBegin, + "Running health checks"); + + private static readonly Action _healthCheckProcessingEnd = LoggerMessage.Define( + LogLevel.Debug, + EventIds.HealthCheckProcessingEnd, + "Health check processing completed after {ElapsedMilliseconds}ms with combined status {HealthStatus}"); + + private static readonly Action _healthCheckBegin = LoggerMessage.Define( + LogLevel.Debug, + EventIds.HealthCheckBegin, + "Running health check {HealthCheckName}"); + + // These are separate so they can have different log levels + private static readonly string HealthCheckEndText = "Health check {HealthCheckName} completed after {ElapsedMilliseconds}ms with status {HealthStatus} and '{HealthCheckDescription}'"; + + private static readonly Action _healthCheckEndHealthy = LoggerMessage.Define( + LogLevel.Debug, + EventIds.HealthCheckEnd, + HealthCheckEndText); + + private static readonly Action _healthCheckEndDegraded = LoggerMessage.Define( + LogLevel.Warning, + EventIds.HealthCheckEnd, + HealthCheckEndText); + + private static readonly Action _healthCheckEndUnhealthy = LoggerMessage.Define( + LogLevel.Error, + EventIds.HealthCheckEnd, + HealthCheckEndText); + + private static readonly Action _healthCheckEndFailed = LoggerMessage.Define( + LogLevel.Error, + EventIds.HealthCheckEnd, + HealthCheckEndText); + + private static readonly Action _healthCheckError = LoggerMessage.Define( + LogLevel.Error, + EventIds.HealthCheckError, + "Health check {HealthCheckName} threw an unhandled exception after {ElapsedMilliseconds}ms"); + + public static void HealthCheckProcessingBegin(ILogger logger) + { + _healthCheckProcessingBegin(logger, null); + } + + public static void HealthCheckProcessingEnd(ILogger logger, HealthStatus status, TimeSpan duration) + { + _healthCheckProcessingEnd(logger, duration.TotalMilliseconds, status, null); + } + + public static void HealthCheckBegin(ILogger logger, HealthCheckRegistration registration) + { + _healthCheckBegin(logger, registration.Name, null); + } + + public static void HealthCheckEnd(ILogger logger, HealthCheckRegistration registration, HealthReportEntry entry, TimeSpan duration) + { + switch (entry.Status) + { + case HealthStatus.Healthy: + _healthCheckEndHealthy(logger, registration.Name, duration.TotalMilliseconds, entry.Status, entry.Description, null); + break; + + case HealthStatus.Degraded: + _healthCheckEndDegraded(logger, registration.Name, duration.TotalMilliseconds, entry.Status, entry.Description, null); + break; + + case HealthStatus.Unhealthy: + _healthCheckEndUnhealthy(logger, registration.Name, duration.TotalMilliseconds, entry.Status, entry.Description, null); + break; + } + } + + public static void HealthCheckError(ILogger logger, HealthCheckRegistration registration, Exception exception, TimeSpan duration) + { + _healthCheckError(logger, registration.Name, duration.TotalMilliseconds, exception); + } + + public static void HealthCheckData(ILogger logger, HealthCheckRegistration registration, HealthReportEntry entry) + { + if (entry.Data.Count > 0 && logger.IsEnabled(LogLevel.Debug)) + { + logger.Log( + LogLevel.Debug, + EventIds.HealthCheckData, + new HealthCheckDataLogValue(registration.Name, entry.Data), + null, + (state, ex) => state.ToString()); + } + } + } + + internal class HealthCheckDataLogValue : IReadOnlyList> + { + private readonly string _name; + private readonly List> _values; + + private string _formatted; + + public HealthCheckDataLogValue(string name, IReadOnlyDictionary values) + { + _name = name; + _values = values.ToList(); + + // We add the name as a kvp so that you can filter by health check name in the logs. + // This is the same parameter name used in the other logs. + _values.Add(new KeyValuePair("HealthCheckName", name)); + } + + public KeyValuePair this[int index] + { + get + { + if (index < 0 || index >= Count) + { + throw new IndexOutOfRangeException(nameof(index)); + } + + return _values[index]; + } + } + + public int Count => _values.Count; + + public IEnumerator> GetEnumerator() + { + return _values.GetEnumerator(); + } + + IEnumerator IEnumerable.GetEnumerator() + { + return _values.GetEnumerator(); + } + + public override string ToString() + { + if (_formatted == null) + { + var builder = new StringBuilder(); + builder.AppendLine($"Health check data for {_name}:"); + + var values = _values; + for (var i = 0; i < values.Count; i++) + { + var kvp = values[i]; + builder.Append(" "); + builder.Append(kvp.Key); + builder.Append(": "); + + builder.AppendLine(kvp.Value?.ToString()); + } + + _formatted = builder.ToString(); + } + + return _formatted; + } + } + } +} diff --git a/src/HealthChecks/HealthChecks/src/DelegateHealthCheck.cs b/src/HealthChecks/HealthChecks/src/DelegateHealthCheck.cs new file mode 100644 index 0000000000..94069fd7d1 --- /dev/null +++ b/src/HealthChecks/HealthChecks/src/DelegateHealthCheck.cs @@ -0,0 +1,35 @@ +// 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.Threading; +using System.Threading.Tasks; + +namespace Microsoft.Extensions.Diagnostics.HealthChecks +{ + /// + /// A simple implementation of which uses a provided delegate to + /// implement the check. + /// + internal sealed class DelegateHealthCheck : IHealthCheck + { + private readonly Func> _check; + + /// + /// Create an instance of from the specified delegate. + /// + /// A delegate which provides the code to execute when the health check is run. + public DelegateHealthCheck(Func> check) + { + _check = check ?? throw new ArgumentNullException(nameof(check)); + } + + /// + /// Runs the health check, returning the status of the component being checked. + /// + /// A context object associated with the current execution. + /// A that can be used to cancel the health check. + /// A that completes when the health check has finished, yielding the status of the component being checked. + public Task CheckHealthAsync(HealthCheckContext context, CancellationToken cancellationToken = default) => _check(cancellationToken); + } +} diff --git a/src/HealthChecks/HealthChecks/src/DependencyInjection/HealthCheckServiceCollectionExtensions.cs b/src/HealthChecks/HealthChecks/src/DependencyInjection/HealthCheckServiceCollectionExtensions.cs new file mode 100644 index 0000000000..d6df03d2ae --- /dev/null +++ b/src/HealthChecks/HealthChecks/src/DependencyInjection/HealthCheckServiceCollectionExtensions.cs @@ -0,0 +1,33 @@ +// 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 Microsoft.Extensions.DependencyInjection.Extensions; +using Microsoft.Extensions.Diagnostics.HealthChecks; +using Microsoft.Extensions.Hosting; + +namespace Microsoft.Extensions.DependencyInjection +{ + /// + /// Provides extension methods for registering in an . + /// + public static class HealthCheckServiceCollectionExtensions + { + /// + /// Adds the to the container, using the provided delegate to register + /// health checks. + /// + /// + /// This operation is idempotent - multiple invocations will still only result in a single + /// instance in the . It can be invoked + /// multiple times in order to get access to the in multiple places. + /// + /// The to add the to. + /// An instance of from which health checks can be registered. + public static IHealthChecksBuilder AddHealthChecks(this IServiceCollection services) + { + services.TryAddSingleton(); + services.TryAddSingleton(); + return new HealthChecksBuilder(services); + } + } +} diff --git a/src/HealthChecks/HealthChecks/src/DependencyInjection/HealthChecksBuilder.cs b/src/HealthChecks/HealthChecks/src/DependencyInjection/HealthChecksBuilder.cs new file mode 100644 index 0000000000..231dd51717 --- /dev/null +++ b/src/HealthChecks/HealthChecks/src/DependencyInjection/HealthChecksBuilder.cs @@ -0,0 +1,33 @@ +// 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 Microsoft.Extensions.Diagnostics.HealthChecks; + +namespace Microsoft.Extensions.DependencyInjection +{ + internal class HealthChecksBuilder : IHealthChecksBuilder + { + public HealthChecksBuilder(IServiceCollection services) + { + Services = services; + } + + public IServiceCollection Services { get; } + + public IHealthChecksBuilder Add(HealthCheckRegistration registration) + { + if (registration == null) + { + throw new ArgumentNullException(nameof(registration)); + } + + Services.Configure(options => + { + options.Registrations.Add(registration); + }); + + return this; + } + } +} diff --git a/src/HealthChecks/HealthChecks/src/DependencyInjection/HealthChecksBuilderAddCheckExtensions.cs b/src/HealthChecks/HealthChecks/src/DependencyInjection/HealthChecksBuilderAddCheckExtensions.cs new file mode 100644 index 0000000000..9508889054 --- /dev/null +++ b/src/HealthChecks/HealthChecks/src/DependencyInjection/HealthChecksBuilderAddCheckExtensions.cs @@ -0,0 +1,191 @@ +// 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.Collections.Generic; +using Microsoft.Extensions.Diagnostics.HealthChecks; + +namespace Microsoft.Extensions.DependencyInjection +{ + /// + /// Provides basic extension methods for registering instances in an . + /// + public static class HealthChecksBuilderAddCheckExtensions + { + /// + /// Adds a new health check with the specified name and implementation. + /// + /// The . + /// The name of the health check. + /// An instance. + /// + /// The that should be reported when the health check reports a failure. If the provided value + /// is null, then will be reported. + /// + /// A list of tags that can be used to filter health checks. + /// The . + public static IHealthChecksBuilder AddCheck( + this IHealthChecksBuilder builder, + string name, + IHealthCheck instance, + HealthStatus? failureStatus = null, + IEnumerable tags = null) + { + if (builder == null) + { + throw new ArgumentNullException(nameof(builder)); + } + + if (name == null) + { + throw new ArgumentNullException(nameof(name)); + } + + if (instance == null) + { + throw new ArgumentNullException(nameof(instance)); + } + + return builder.Add(new HealthCheckRegistration(name, instance, failureStatus, tags)); + } + + /// + /// Adds a new health check with the specified name and implementation. + /// + /// The health check implementation type. + /// The . + /// The name of the health check. + /// + /// The that should be reported when the health check reports a failure. If the provided value + /// is null, then will be reported. + /// + /// A list of tags that can be used to filter health checks. + /// The . + /// + /// This method will use to create the health check + /// instance when needed. If a service of type is registred in the dependency injection container + /// with any liftime it will be used. Otherwise an instance of type will be constructed with + /// access to services from the dependency injection container. + /// + public static IHealthChecksBuilder AddCheck( + this IHealthChecksBuilder builder, + string name, + HealthStatus? failureStatus = null, + IEnumerable tags = null) where T : class, IHealthCheck + { + if (builder == null) + { + throw new ArgumentNullException(nameof(builder)); + } + + if (name == null) + { + throw new ArgumentNullException(nameof(name)); + } + + return builder.Add(new HealthCheckRegistration(name, s => ActivatorUtilities.GetServiceOrCreateInstance(s), failureStatus, tags)); + } + + // NOTE: AddTypeActivatedCheck has overloads rather than default parameters values, because default parameter values don't + // play super well with params. + + /// + /// Adds a new type activated health check with the specified name and implementation. + /// + /// The health check implementation type. + /// The . + /// The name of the health check. + /// Additional arguments to provide to the constructor. + /// The . + /// + /// This method will use to create the health check + /// instance when needed. Additional arguments can be provided to the constructor via . + /// + public static IHealthChecksBuilder AddTypeActivatedCheck(this IHealthChecksBuilder builder, string name, params object[] args) where T : class, IHealthCheck + { + if (builder == null) + { + throw new ArgumentNullException(nameof(builder)); + } + + if (name == null) + { + throw new ArgumentNullException(nameof(name)); + } + + return AddTypeActivatedCheck(builder, name, failureStatus: null, tags: null); + } + + /// + /// Adds a new type activated health check with the specified name and implementation. + /// + /// The health check implementation type. + /// The . + /// The name of the health check. + /// + /// The that should be reported when the health check reports a failure. If the provided value + /// is null, then will be reported. + /// + /// Additional arguments to provide to the constructor. + /// The . + /// + /// This method will use to create the health check + /// instance when needed. Additional arguments can be provided to the constructor via . + /// + public static IHealthChecksBuilder AddTypeActivatedCheck( + this IHealthChecksBuilder builder, + string name, + HealthStatus? failureStatus, + params object[] args) where T : class, IHealthCheck + { + if (builder == null) + { + throw new ArgumentNullException(nameof(builder)); + } + + if (name == null) + { + throw new ArgumentNullException(nameof(name)); + } + + return AddTypeActivatedCheck(builder, name, failureStatus, tags: null); + } + + /// + /// Adds a new type activated health check with the specified name and implementation. + /// + /// The health check implementation type. + /// The . + /// The name of the health check. + /// + /// The that should be reported when the health check reports a failure. If the provided value + /// is null, then will be reported. + /// + /// A list of tags that can be used to filter health checks. + /// Additional arguments to provide to the constructor. + /// The . + /// + /// This method will use to create the health check + /// instance when needed. Additional arguments can be provided to the constructor via . + /// + public static IHealthChecksBuilder AddTypeActivatedCheck( + this IHealthChecksBuilder builder, + string name, + HealthStatus? failureStatus, + IEnumerable tags, + params object[] args) where T : class, IHealthCheck + { + if (builder == null) + { + throw new ArgumentNullException(nameof(builder)); + } + + if (name == null) + { + throw new ArgumentNullException(nameof(name)); + } + + return builder.Add(new HealthCheckRegistration(name, s => ActivatorUtilities.CreateInstance(s, args), failureStatus, tags)); + } + } +} diff --git a/src/HealthChecks/HealthChecks/src/DependencyInjection/HealthChecksBuilderDelegateExtensions.cs b/src/HealthChecks/HealthChecks/src/DependencyInjection/HealthChecksBuilderDelegateExtensions.cs new file mode 100644 index 0000000000..d7dfdd90ae --- /dev/null +++ b/src/HealthChecks/HealthChecks/src/DependencyInjection/HealthChecksBuilderDelegateExtensions.cs @@ -0,0 +1,149 @@ +// 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.Collections.Generic; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.Extensions.Diagnostics.HealthChecks; + +namespace Microsoft.Extensions.DependencyInjection +{ + /// + /// Provides extension methods for registering delegates with the . + /// + public static class HealthChecksBuilderDelegateExtensions + { + /// + /// Adds a new health check with the specified name and implementation. + /// + /// The . + /// The name of the health check. + /// A list of tags that can be used to filter health checks. + /// A delegate that provides the health check implementation. + /// The . + public static IHealthChecksBuilder AddCheck( + this IHealthChecksBuilder builder, + string name, + Func check, + IEnumerable tags = null) + { + if (builder == null) + { + throw new ArgumentNullException(nameof(builder)); + } + + if (name == null) + { + throw new ArgumentNullException(nameof(name)); + } + + if (check == null) + { + throw new ArgumentNullException(nameof(check)); + } + + var instance = new DelegateHealthCheck((ct) => Task.FromResult(check())); + return builder.Add(new HealthCheckRegistration(name, instance, failureStatus: null, tags)); + } + + /// + /// Adds a new health check with the specified name and implementation. + /// + /// The . + /// The name of the health check. + /// A list of tags that can be used to filter health checks. + /// A delegate that provides the health check implementation. + /// The . + public static IHealthChecksBuilder AddCheck( + this IHealthChecksBuilder builder, + string name, + Func check, + IEnumerable tags = null) + { + if (builder == null) + { + throw new ArgumentNullException(nameof(builder)); + } + + if (name == null) + { + throw new ArgumentNullException(nameof(name)); + } + + if (check == null) + { + throw new ArgumentNullException(nameof(check)); + } + + var instance = new DelegateHealthCheck((ct) => Task.FromResult(check(ct))); + return builder.Add(new HealthCheckRegistration(name, instance, failureStatus: null, tags)); + } + + /// + /// Adds a new health check with the specified name and implementation. + /// + /// The . + /// The name of the health check. + /// A list of tags that can be used to filter health checks. + /// A delegate that provides the health check implementation. + /// The . + public static IHealthChecksBuilder AddAsyncCheck( + this IHealthChecksBuilder builder, + string name, + Func> check, + IEnumerable tags = null) + { + if (builder == null) + { + throw new ArgumentNullException(nameof(builder)); + } + + if (name == null) + { + throw new ArgumentNullException(nameof(name)); + } + + if (check == null) + { + throw new ArgumentNullException(nameof(check)); + } + + var instance = new DelegateHealthCheck((ct) => check()); + return builder.Add(new HealthCheckRegistration(name, instance, failureStatus: null, tags)); + } + + /// + /// Adds a new health check with the specified name and implementation. + /// + /// The . + /// The name of the health check. + /// A list of tags that can be used to filter health checks. + /// A delegate that provides the health check implementation. + /// The . + public static IHealthChecksBuilder AddAsyncCheck( + this IHealthChecksBuilder builder, + string name, + Func> check, + IEnumerable tags = null) + { + if (builder == null) + { + throw new ArgumentNullException(nameof(builder)); + } + + if (name == null) + { + throw new ArgumentNullException(nameof(name)); + } + + if (check == null) + { + throw new ArgumentNullException(nameof(check)); + } + + var instance = new DelegateHealthCheck((ct) => check(ct)); + return builder.Add(new HealthCheckRegistration(name, instance, failureStatus: null, tags)); + } + } +} diff --git a/src/HealthChecks/HealthChecks/src/DependencyInjection/IHealthChecksBuilder.cs b/src/HealthChecks/HealthChecks/src/DependencyInjection/IHealthChecksBuilder.cs new file mode 100644 index 0000000000..eb78293f87 --- /dev/null +++ b/src/HealthChecks/HealthChecks/src/DependencyInjection/IHealthChecksBuilder.cs @@ -0,0 +1,24 @@ +// 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 Microsoft.Extensions.Diagnostics.HealthChecks; + +namespace Microsoft.Extensions.DependencyInjection +{ + /// + /// A builder used to register health checks. + /// + public interface IHealthChecksBuilder + { + /// + /// Adds a for a health check. + /// + /// The . + IHealthChecksBuilder Add(HealthCheckRegistration registration); + + /// + /// Gets the into which instances should be registered. + /// + IServiceCollection Services { get; } + } +} diff --git a/src/HealthChecks/HealthChecks/src/HealthCheckLogScope.cs b/src/HealthChecks/HealthChecks/src/HealthCheckLogScope.cs new file mode 100644 index 0000000000..c7ef3ff5bd --- /dev/null +++ b/src/HealthChecks/HealthChecks/src/HealthCheckLogScope.cs @@ -0,0 +1,48 @@ +// 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.Collections; +using System.Collections.Generic; + +namespace Microsoft.Extensions.Diagnostics.HealthChecks +{ + internal class HealthCheckLogScope : IReadOnlyList> + { + public string HealthCheckName { get; } + + int IReadOnlyCollection>.Count { get; } = 1; + + KeyValuePair IReadOnlyList>.this[int index] + { + get + { + if (index == 0) + { + return new KeyValuePair(nameof(HealthCheckName), HealthCheckName); + } + + throw new ArgumentOutOfRangeException(nameof(index)); + } + } + + /// + /// Creates a new instance of with the provided name. + /// + /// The name of the health check being executed. + public HealthCheckLogScope(string healthCheckName) + { + HealthCheckName = healthCheckName; + } + + IEnumerator> IEnumerable>.GetEnumerator() + { + yield return new KeyValuePair(nameof(HealthCheckName), HealthCheckName); + } + + IEnumerator IEnumerable.GetEnumerator() + { + return ((IEnumerable>)this).GetEnumerator(); + } + } +} diff --git a/src/HealthChecks/HealthChecks/src/HealthCheckPublisherHostedService.cs b/src/HealthChecks/HealthChecks/src/HealthCheckPublisherHostedService.cs new file mode 100644 index 0000000000..d124ffa2e3 --- /dev/null +++ b/src/HealthChecks/HealthChecks/src/HealthCheckPublisherHostedService.cs @@ -0,0 +1,262 @@ +// 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.Collections.Generic; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.Extensions.Hosting; +using Microsoft.Extensions.Internal; +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Options; + +namespace Microsoft.Extensions.Diagnostics.HealthChecks +{ + internal sealed class HealthCheckPublisherHostedService : IHostedService + { + private readonly HealthCheckService _healthCheckService; + private readonly IOptions _options; + private readonly ILogger _logger; + private readonly IHealthCheckPublisher[] _publishers; + + private CancellationTokenSource _stopping; + private Timer _timer; + + public HealthCheckPublisherHostedService( + HealthCheckService healthCheckService, + IOptions options, + ILogger logger, + IEnumerable publishers) + { + if (healthCheckService == null) + { + throw new ArgumentNullException(nameof(healthCheckService)); + } + + if (options == null) + { + throw new ArgumentNullException(nameof(options)); + } + + if (logger == null) + { + throw new ArgumentNullException(nameof(logger)); + } + + if (publishers == null) + { + throw new ArgumentNullException(nameof(publishers)); + } + + _healthCheckService = healthCheckService; + _options = options; + _logger = logger; + _publishers = publishers.ToArray(); + + _stopping = new CancellationTokenSource(); + } + + internal bool IsStopping => _stopping.IsCancellationRequested; + + internal bool IsTimerRunning => _timer != null; + + public Task StartAsync(CancellationToken cancellationToken = default) + { + if (_publishers.Length == 0) + { + return Task.CompletedTask; + } + + // IMPORTANT - make sure this is the last thing that happens in this method. The timer can + // fire before other code runs. + _timer = NonCapturingTimer.Create(Timer_Tick, null, dueTime: _options.Value.Delay, period: _options.Value.Period); + + return Task.CompletedTask; + } + + public Task StopAsync(CancellationToken cancellationToken = default) + { + try + { + _stopping.Cancel(); + } + catch + { + // Ignore exceptions thrown as a result of a cancellation. + } + + if (_publishers.Length == 0) + { + return Task.CompletedTask; + } + + _timer?.Dispose(); + _timer = null; + + + return Task.CompletedTask; + } + + // Yes, async void. We need to be async. We need to be void. We handle the exceptions in RunAsync + private async void Timer_Tick(object state) + { + await RunAsync(); + } + + // Internal for testing + internal async Task RunAsync() + { + var duration = ValueStopwatch.StartNew(); + Logger.HealthCheckPublisherProcessingBegin(_logger); + + CancellationTokenSource cancellation = null; + try + { + var timeout = _options.Value.Timeout; + + cancellation = CancellationTokenSource.CreateLinkedTokenSource(_stopping.Token); + cancellation.CancelAfter(timeout); + + await RunAsyncCore(cancellation.Token); + + Logger.HealthCheckPublisherProcessingEnd(_logger, duration.GetElapsedTime()); + } + catch (OperationCanceledException) when (IsStopping) + { + // This is a cancellation - if the app is shutting down we want to ignore it. Otherwise, it's + // a timeout and we want to log it. + } + catch (Exception ex) + { + // This is an error, publishing failed. + Logger.HealthCheckPublisherProcessingEnd(_logger, duration.GetElapsedTime(), ex); + } + finally + { + cancellation.Dispose(); + } + } + + private async Task RunAsyncCore(CancellationToken cancellationToken) + { + // Forcibly yield - we want to unblock the timer thread. + await Task.Yield(); + + // The health checks service does it's own logging, and doesn't throw exceptions. + var report = await _healthCheckService.CheckHealthAsync(_options.Value.Predicate, cancellationToken); + + var publishers = _publishers; + var tasks = new Task[publishers.Length]; + for (var i = 0; i < publishers.Length; i++) + { + tasks[i] = RunPublisherAsync(publishers[i], report, cancellationToken); + } + + await Task.WhenAll(tasks); + } + + private async Task RunPublisherAsync(IHealthCheckPublisher publisher, HealthReport report, CancellationToken cancellationToken) + { + var duration = ValueStopwatch.StartNew(); + + try + { + Logger.HealthCheckPublisherBegin(_logger, publisher); + + await publisher.PublishAsync(report, cancellationToken); + Logger.HealthCheckPublisherEnd(_logger, publisher, duration.GetElapsedTime()); + } + catch (OperationCanceledException) when (IsStopping) + { + // This is a cancellation - if the app is shutting down we want to ignore it. Otherwise, it's + // a timeout and we want to log it. + } + catch (OperationCanceledException ocex) + { + Logger.HealthCheckPublisherTimeout(_logger, publisher, duration.GetElapsedTime()); + throw ocex; + } + catch (Exception ex) + { + Logger.HealthCheckPublisherError(_logger, publisher, duration.GetElapsedTime(), ex); + throw ex; + } + } + + internal static class EventIds + { + public static readonly EventId HealthCheckPublisherProcessingBegin = new EventId(100, "HealthCheckPublisherProcessingBegin"); + public static readonly EventId HealthCheckPublisherProcessingEnd = new EventId(101, "HealthCheckPublisherProcessingEnd"); + public static readonly EventId HealthCheckPublisherProcessingError = new EventId(101, "HealthCheckPublisherProcessingError"); + + public static readonly EventId HealthCheckPublisherBegin = new EventId(102, "HealthCheckPublisherBegin"); + public static readonly EventId HealthCheckPublisherEnd = new EventId(103, "HealthCheckPublisherEnd"); + public static readonly EventId HealthCheckPublisherError = new EventId(104, "HealthCheckPublisherError"); + public static readonly EventId HealthCheckPublisherTimeout = new EventId(104, "HealthCheckPublisherTimeout"); + } + + private static class Logger + { + private static readonly Action _healthCheckPublisherProcessingBegin = LoggerMessage.Define( + LogLevel.Debug, + EventIds.HealthCheckPublisherProcessingBegin, + "Running health check publishers"); + + private static readonly Action _healthCheckPublisherProcessingEnd = LoggerMessage.Define( + LogLevel.Debug, + EventIds.HealthCheckPublisherProcessingEnd, + "Health check publisher processing completed after {ElapsedMilliseconds}ms"); + + private static readonly Action _healthCheckPublisherBegin = LoggerMessage.Define( + LogLevel.Debug, + EventIds.HealthCheckPublisherBegin, + "Running health check publisher '{HealthCheckPublisher}'"); + + private static readonly Action _healthCheckPublisherEnd = LoggerMessage.Define( + LogLevel.Debug, + EventIds.HealthCheckPublisherEnd, + "Health check '{HealthCheckPublisher}' completed after {ElapsedMilliseconds}ms"); + + private static readonly Action _healthCheckPublisherError = LoggerMessage.Define( + LogLevel.Error, + EventIds.HealthCheckPublisherError, + "Health check {HealthCheckPublisher} threw an unhandled exception after {ElapsedMilliseconds}ms"); + + private static readonly Action _healthCheckPublisherTimeout = LoggerMessage.Define( + LogLevel.Error, + EventIds.HealthCheckPublisherTimeout, + "Health check {HealthCheckPublisher} was canceled after {ElapsedMilliseconds}ms"); + + public static void HealthCheckPublisherProcessingBegin(ILogger logger) + { + _healthCheckPublisherProcessingBegin(logger, null); + } + + public static void HealthCheckPublisherProcessingEnd(ILogger logger, TimeSpan duration, Exception exception = null) + { + _healthCheckPublisherProcessingEnd(logger, duration.TotalMilliseconds, exception); + } + + public static void HealthCheckPublisherBegin(ILogger logger, IHealthCheckPublisher publisher) + { + _healthCheckPublisherBegin(logger, publisher, null); + } + + public static void HealthCheckPublisherEnd(ILogger logger, IHealthCheckPublisher publisher, TimeSpan duration) + { + _healthCheckPublisherEnd(logger, publisher, duration.TotalMilliseconds, null); + } + + public static void HealthCheckPublisherError(ILogger logger, IHealthCheckPublisher publisher, TimeSpan duration, Exception exception) + { + _healthCheckPublisherError(logger, publisher, duration.TotalMilliseconds, exception); + } + + public static void HealthCheckPublisherTimeout(ILogger logger, IHealthCheckPublisher publisher, TimeSpan duration) + { + _healthCheckPublisherTimeout(logger, publisher, duration.TotalMilliseconds, null); + } + } + } +} diff --git a/src/HealthChecks/HealthChecks/src/HealthCheckPublisherOptions.cs b/src/HealthChecks/HealthChecks/src/HealthCheckPublisherOptions.cs new file mode 100644 index 0000000000..1313718af8 --- /dev/null +++ b/src/HealthChecks/HealthChecks/src/HealthCheckPublisherOptions.cs @@ -0,0 +1,84 @@ +// 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; + +namespace Microsoft.Extensions.Diagnostics.HealthChecks +{ + /// + /// Options for the default service that executes instances. + /// + public sealed class HealthCheckPublisherOptions + { + private TimeSpan _delay; + private TimeSpan _period; + + public HealthCheckPublisherOptions() + { + _delay = TimeSpan.FromSeconds(5); + _period = TimeSpan.FromSeconds(30); + } + + /// + /// Gets or sets the initial delay applied after the application starts before executing + /// instances. The delay is applied once at startup, and does + /// not apply to subsequent iterations. The default value is 5 seconds. + /// + public TimeSpan Delay + { + get => _delay; + set + { + if (value == System.Threading.Timeout.InfiniteTimeSpan) + { + throw new ArgumentException($"The {nameof(Delay)} must not be infinite.", nameof(value)); + } + + _delay = value; + } + } + + /// + /// Gets or sets the period of execution. The default value is + /// 30 seconds. + /// + /// + /// The cannot be set to a value lower than 1 second. + /// + public TimeSpan Period + { + get => _period; + set + { + if (value < TimeSpan.FromSeconds(1)) + { + throw new ArgumentException($"The {nameof(Period)} must be greater than or equal to one second.", nameof(value)); + } + + if (value == System.Threading.Timeout.InfiniteTimeSpan) + { + throw new ArgumentException($"The {nameof(Period)} must not be infinite.", nameof(value)); + } + + _delay = value; + } + } + + /// + /// Gets or sets a predicate that is used to filter the set of health checks executed. + /// + /// + /// If is null, the health check publisher service will run all + /// registered health checks - this is the default behavior. To run a subset of health checks, + /// provide a function that filters the set of checks. The predicate will be evaluated each period. + /// + public Func Predicate { get; set; } + + /// + /// Gets or sets the timeout for executing the health checks an all + /// instances. Use to execute with no timeout. + /// The default value is 30 seconds. + /// + public TimeSpan Timeout { get; set; } = TimeSpan.FromSeconds(30); + } +} diff --git a/src/HealthChecks/HealthChecks/src/HealthCheckService.cs b/src/HealthChecks/HealthChecks/src/HealthCheckService.cs new file mode 100644 index 0000000000..e4a128148d --- /dev/null +++ b/src/HealthChecks/HealthChecks/src/HealthCheckService.cs @@ -0,0 +1,61 @@ +// 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.Threading; +using System.Threading.Tasks; +using Microsoft.Extensions.DependencyInjection; + +namespace Microsoft.Extensions.Diagnostics.HealthChecks +{ + /// + /// A service which can be used to check the status of instances + /// registered in the application. + /// + /// + /// + /// The default implementation of is registered in the dependency + /// injection container as a singleton service by calling + /// . + /// + /// + /// The returned by + /// + /// provides a convenience API for registering health checks. + /// + /// + /// implementations can be registered through extension methods provided by + /// . + /// + /// + public abstract class HealthCheckService + { + /// + /// Runs all the health checks in the application and returns the aggregated status. + /// + /// A which can be used to cancel the health checks. + /// + /// A which will complete when all the health checks have been run, + /// yielding a containing the results. + /// + public Task CheckHealthAsync(CancellationToken cancellationToken = default) + { + return CheckHealthAsync(predicate: null, cancellationToken); + } + + /// + /// Runs the provided health checks and returns the aggregated status + /// + /// + /// A predicate that can be used to include health checks based on user-defined criteria. + /// + /// A which can be used to cancel the health checks. + /// + /// A which will complete when all the health checks have been run, + /// yielding a containing the results. + /// + public abstract Task CheckHealthAsync( + Func predicate, + CancellationToken cancellationToken = default); + } +} diff --git a/src/HealthChecks/HealthChecks/src/HealthCheckServiceOptions.cs b/src/HealthChecks/HealthChecks/src/HealthCheckServiceOptions.cs new file mode 100644 index 0000000000..b8dfdb9b40 --- /dev/null +++ b/src/HealthChecks/HealthChecks/src/HealthCheckServiceOptions.cs @@ -0,0 +1,18 @@ +// 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.Collections.Generic; + +namespace Microsoft.Extensions.Diagnostics.HealthChecks +{ + /// + /// Options for the default implementation of + /// + public sealed class HealthCheckServiceOptions + { + /// + /// Gets the health check registrations. + /// + public ICollection Registrations { get; } = new List(); + } +} diff --git a/src/HealthChecks/HealthChecks/src/Microsoft.Extensions.Diagnostics.HealthChecks.csproj b/src/HealthChecks/HealthChecks/src/Microsoft.Extensions.Diagnostics.HealthChecks.csproj new file mode 100644 index 0000000000..d0b1c97ef0 --- /dev/null +++ b/src/HealthChecks/HealthChecks/src/Microsoft.Extensions.Diagnostics.HealthChecks.csproj @@ -0,0 +1,26 @@ + + + Components for performing health checks in .NET applications + +Commonly Used Types: +Microsoft.Extensions.Diagnostics.HealthChecks.HealthCheckService +Microsoft.Extensions.Diagnostics.HealthChecks.IHealthChecksBuilder + + netstandard2.0 + $(NoWarn);CS1591 + true + diagnostics;healthchecks + + + + + + + + + + + + + + diff --git a/src/HealthChecks/HealthChecks/src/Properties/AssemblyInfo.cs b/src/HealthChecks/HealthChecks/src/Properties/AssemblyInfo.cs new file mode 100644 index 0000000000..13e969bfad --- /dev/null +++ b/src/HealthChecks/HealthChecks/src/Properties/AssemblyInfo.cs @@ -0,0 +1,3 @@ +using System.Runtime.CompilerServices; + +[assembly: InternalsVisibleTo("Microsoft.Extensions.Diagnostics.HealthChecks.Tests, PublicKey=0024000004800000940000000602000000240000525341310004000001000100f33a29044fa9d740c9b3213a93e57c84b472c84e0b8a0e1ae48e67a9f8f6de9d5f7f3d52ac23e48ac51801f1dc950abe901da34d2a9e3baadb141a17c77ef3c565dd5ee5054b91cf63bb3c6ab83f72ab3aafe93d0fc3c2348b764fafb0b1c0733de51459aeab46580384bf9d74c4e28164b7cde247f891ba07891c9d872ad2bb")] \ No newline at end of file diff --git a/src/HealthChecks/HealthChecks/src/baseline.netcore.json b/src/HealthChecks/HealthChecks/src/baseline.netcore.json new file mode 100644 index 0000000000..cb2fe053f1 --- /dev/null +++ b/src/HealthChecks/HealthChecks/src/baseline.netcore.json @@ -0,0 +1,5 @@ +{ + "AssemblyIdentity": "Microsoft.Extensions.Diagnostics.HealthChecks, Version=2.1.1.0, Culture=neutral, PublicKeyToken=adb9793829ddae60", + "Types": [ + ] +} \ No newline at end of file diff --git a/src/HealthChecks/HealthChecks/test/DefaultHealthCheckServiceTest.cs b/src/HealthChecks/HealthChecks/test/DefaultHealthCheckServiceTest.cs new file mode 100644 index 0000000000..9ab991204e --- /dev/null +++ b/src/HealthChecks/HealthChecks/test/DefaultHealthCheckServiceTest.cs @@ -0,0 +1,419 @@ +// 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.Collections.Generic; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Logging.Testing; +using Microsoft.Extensions.Options; +using Xunit; + +namespace Microsoft.Extensions.Diagnostics.HealthChecks +{ + public class DefaultHealthCheckServiceTest + { + [Fact] + public void Constructor_ThrowsUsefulExceptionForDuplicateNames() + { + // Arrange + // + // Doing this the old fashioned way so we can verify that the exception comes + // from the constructor. + var serviceCollection = new ServiceCollection(); + serviceCollection.AddLogging(); + serviceCollection.AddOptions(); + serviceCollection.AddHealthChecks() + .AddCheck("Foo", new DelegateHealthCheck(_ => Task.FromResult(HealthCheckResult.Healthy()))) + .AddCheck("Foo", new DelegateHealthCheck(_ => Task.FromResult(HealthCheckResult.Healthy()))) + .AddCheck("Bar", new DelegateHealthCheck(_ => Task.FromResult(HealthCheckResult.Healthy()))) + .AddCheck("Baz", new DelegateHealthCheck(_ => Task.FromResult(HealthCheckResult.Healthy()))) + .AddCheck("Baz", new DelegateHealthCheck(_ => Task.FromResult(HealthCheckResult.Healthy()))); + + var services = serviceCollection.BuildServiceProvider(); + + var scopeFactory = services.GetRequiredService(); + var options = services.GetRequiredService>(); + var logger = services.GetRequiredService>(); + + // Act + var exception = Assert.Throws(() => new DefaultHealthCheckService(scopeFactory, options, logger)); + + // Assert + Assert.StartsWith($"Duplicate health checks were registered with the name(s): Foo, Baz", exception.Message); + } + + [Fact] + public async Task CheckAsync_RunsAllChecksAndAggregatesResultsAsync() + { + const string DataKey = "Foo"; + const string DataValue = "Bar"; + const string DegradedMessage = "I'm not feeling so good"; + const string UnhealthyMessage = "Halp!"; + const string HealthyMessage = "Everything is A-OK"; + var exception = new Exception("Things are pretty bad!"); + + // Arrange + var data = new Dictionary() + { + { DataKey, DataValue } + }; + + var service = CreateHealthChecksService(b => + { + b.AddAsyncCheck("HealthyCheck", _ => Task.FromResult(HealthCheckResult.Healthy(HealthyMessage, data))); + b.AddAsyncCheck("DegradedCheck", _ => Task.FromResult(HealthCheckResult.Degraded(DegradedMessage))); + b.AddAsyncCheck("UnhealthyCheck", _ => Task.FromResult(HealthCheckResult.Unhealthy(UnhealthyMessage, exception))); + }); + + // Act + var results = await service.CheckHealthAsync(); + + // Assert + Assert.Collection( + results.Entries.OrderBy(kvp => kvp.Key), + actual => + { + Assert.Equal("DegradedCheck", actual.Key); + Assert.Equal(DegradedMessage, actual.Value.Description); + Assert.Equal(HealthStatus.Degraded, actual.Value.Status); + Assert.Null(actual.Value.Exception); + Assert.Empty(actual.Value.Data); + }, + actual => + { + Assert.Equal("HealthyCheck", actual.Key); + Assert.Equal(HealthyMessage, actual.Value.Description); + Assert.Equal(HealthStatus.Healthy, actual.Value.Status); + Assert.Null(actual.Value.Exception); + Assert.Collection(actual.Value.Data, item => + { + Assert.Equal(DataKey, item.Key); + Assert.Equal(DataValue, item.Value); + }); + }, + actual => + { + Assert.Equal("UnhealthyCheck", actual.Key); + Assert.Equal(UnhealthyMessage, actual.Value.Description); + Assert.Equal(HealthStatus.Unhealthy, actual.Value.Status); + Assert.Same(exception, actual.Value.Exception); + Assert.Empty(actual.Value.Data); + }); + } + + [Fact] + public async Task CheckAsync_RunsFilteredChecksAndAggregatesResultsAsync() + { + const string DataKey = "Foo"; + const string DataValue = "Bar"; + const string DegradedMessage = "I'm not feeling so good"; + const string UnhealthyMessage = "Halp!"; + const string HealthyMessage = "Everything is A-OK"; + var exception = new Exception("Things are pretty bad!"); + + // Arrange + var data = new Dictionary + { + { DataKey, DataValue } + }; + + var service = CreateHealthChecksService(b => + { + b.AddAsyncCheck("HealthyCheck", _ => Task.FromResult(HealthCheckResult.Healthy(HealthyMessage, data))); + b.AddAsyncCheck("DegradedCheck", _ => Task.FromResult(HealthCheckResult.Degraded(DegradedMessage))); + b.AddAsyncCheck("UnhealthyCheck", _ => Task.FromResult(HealthCheckResult.Unhealthy(UnhealthyMessage, exception))); + }); + + // Act + var results = await service.CheckHealthAsync(c => c.Name == "HealthyCheck"); + + // Assert + Assert.Collection(results.Entries, + actual => + { + Assert.Equal("HealthyCheck", actual.Key); + Assert.Equal(HealthyMessage, actual.Value.Description); + Assert.Equal(HealthStatus.Healthy, actual.Value.Status); + Assert.Null(actual.Value.Exception); + Assert.Collection(actual.Value.Data, item => + { + Assert.Equal(DataKey, item.Key); + Assert.Equal(DataValue, item.Value); + }); + }); + } + + [Fact] + public async Task CheckHealthAsync_SetsRegistrationForEachCheck() + { + // Arrange + var thrownException = new InvalidOperationException("Whoops!"); + var faultedException = new InvalidOperationException("Ohnoes!"); + + var service = CreateHealthChecksService(b => + { + b.AddCheck("A"); + b.AddCheck("B"); + b.AddCheck("C"); + }); + + // Act + var results = await service.CheckHealthAsync(); + + // Assert + Assert.Collection( + results.Entries, + actual => + { + Assert.Equal("A", actual.Key); + Assert.Collection( + actual.Value.Data, + kvp => Assert.Equal(kvp, new KeyValuePair("name", "A"))); + }, + actual => + { + Assert.Equal("B", actual.Key); + Assert.Collection( + actual.Value.Data, + kvp => Assert.Equal(kvp, new KeyValuePair("name", "B"))); + }, + actual => + { + Assert.Equal("C", actual.Key); + Assert.Collection( + actual.Value.Data, + kvp => Assert.Equal(kvp, new KeyValuePair("name", "C"))); + }); + } + + [Fact] + public async Task CheckHealthAsync_Cancellation_CanPropagate() + { + // Arrange + var insideCheck = new TaskCompletionSource(); + + var service = CreateHealthChecksService(b => + { + b.AddAsyncCheck("cancels", async ct => + { + insideCheck.SetResult(null); + + await Task.Delay(10000, ct); + return HealthCheckResult.Unhealthy(); + }); + }); + + var cancel = new CancellationTokenSource(); + var task = service.CheckHealthAsync(cancel.Token); + + // After this returns we know the check has started + await insideCheck.Task; + + cancel.Cancel(); + + // Act & Assert + await Assert.ThrowsAsync(async () => await task); + } + + [Fact] + public async Task CheckHealthAsync_ConvertsExceptionInHealthCheckToUnhealthyResultAsync() + { + // Arrange + var thrownException = new InvalidOperationException("Whoops!"); + var faultedException = new InvalidOperationException("Ohnoes!"); + + var service = CreateHealthChecksService(b => + { + b.AddAsyncCheck("Throws", ct => throw thrownException); + b.AddAsyncCheck("Faults", ct => Task.FromException(faultedException)); + b.AddAsyncCheck("Succeeds", ct => Task.FromResult(HealthCheckResult.Healthy())); + }); + + // Act + var results = await service.CheckHealthAsync(); + + // Assert + Assert.Collection( + results.Entries, + actual => + { + Assert.Equal("Throws", actual.Key); + Assert.Equal(thrownException.Message, actual.Value.Description); + Assert.Equal(HealthStatus.Unhealthy, actual.Value.Status); + Assert.Same(thrownException, actual.Value.Exception); + }, + actual => + { + Assert.Equal("Faults", actual.Key); + Assert.Equal(faultedException.Message, actual.Value.Description); + Assert.Equal(HealthStatus.Unhealthy, actual.Value.Status); + Assert.Same(faultedException, actual.Value.Exception); + }, + actual => + { + Assert.Equal("Succeeds", actual.Key); + Assert.Null(actual.Value.Description); + Assert.Equal(HealthStatus.Healthy, actual.Value.Status); + Assert.Null(actual.Value.Exception); + }); + } + + [Fact] + public async Task CheckHealthAsync_SetsUpALoggerScopeForEachCheck() + { + // Arrange + var sink = new TestSink(); + var check = new DelegateHealthCheck(cancellationToken => + { + Assert.Collection(sink.Scopes, + actual => + { + Assert.Equal(actual.LoggerName, typeof(DefaultHealthCheckService).FullName); + Assert.Collection((IEnumerable>)actual.Scope, + item => + { + Assert.Equal("HealthCheckName", item.Key); + Assert.Equal("TestScope", item.Value); + }); + }); + return Task.FromResult(HealthCheckResult.Healthy()); + }); + + var loggerFactory = new TestLoggerFactory(sink, enabled: true); + var service = CreateHealthChecksService(b => + { + // Override the logger factory for testing + b.Services.AddSingleton(loggerFactory); + + b.AddCheck("TestScope", check); + }); + + // Act + var results = await service.CheckHealthAsync(); + + // Assert + Assert.Collection(results.Entries, actual => + { + Assert.Equal("TestScope", actual.Key); + Assert.Equal(HealthStatus.Healthy, actual.Value.Status); + }); + } + + [Fact] + public async Task CheckHealthAsync_CheckCanDependOnTransientService() + { + // Arrange + var service = CreateHealthChecksService(b => + { + b.Services.AddTransient(); + + b.AddCheck("Test"); + }); + + // Act + var results = await service.CheckHealthAsync(); + + // Assert + Assert.Collection( + results.Entries, + actual => + { + Assert.Equal("Test", actual.Key); + Assert.Equal(HealthStatus.Healthy, actual.Value.Status); + }); + } + + [Fact] + public async Task CheckHealthAsync_CheckCanDependOnScopedService() + { + // Arrange + var service = CreateHealthChecksService(b => + { + b.Services.AddScoped(); + + b.AddCheck("Test"); + }); + + // Act + var results = await service.CheckHealthAsync(); + + // Assert + Assert.Collection( + results.Entries, + actual => + { + Assert.Equal("Test", actual.Key); + Assert.Equal(HealthStatus.Healthy, actual.Value.Status); + }); + } + + [Fact] + public async Task CheckHealthAsync_CheckCanDependOnSingletonService() + { + // Arrange + var service = CreateHealthChecksService(b => + { + b.Services.AddSingleton(); + + b.AddCheck("Test"); + }); + + // Act + var results = await service.CheckHealthAsync(); + + // Assert + Assert.Collection( + results.Entries, + actual => + { + Assert.Equal("Test", actual.Key); + Assert.Equal(HealthStatus.Healthy, actual.Value.Status); + }); + } + + private static DefaultHealthCheckService CreateHealthChecksService(Action configure) + { + var services = new ServiceCollection(); + services.AddLogging(); + services.AddOptions(); + + var builder = services.AddHealthChecks(); + if (configure != null) + { + configure(builder); + } + + return (DefaultHealthCheckService)services.BuildServiceProvider(validateScopes: true).GetRequiredService(); + } + + private class AnotherService { } + + private class CheckWithServiceDependency : IHealthCheck + { + public CheckWithServiceDependency(AnotherService _) + { + } + + public Task CheckHealthAsync(HealthCheckContext context, CancellationToken cancellationToken = default) + { + return Task.FromResult(HealthCheckResult.Healthy()); + } + } + + private class NameCapturingCheck : IHealthCheck + { + public Task CheckHealthAsync(HealthCheckContext context, CancellationToken cancellationToken = default) + { + var data = new Dictionary() + { + { "name", context.Registration.Name }, + }; + return Task.FromResult(HealthCheckResult.Healthy(data: data)); + } + } + } +} diff --git a/src/HealthChecks/HealthChecks/test/DependencyInjection/HealthChecksBuilderTest.cs b/src/HealthChecks/HealthChecks/test/DependencyInjection/HealthChecksBuilderTest.cs new file mode 100644 index 0000000000..4235f152a2 --- /dev/null +++ b/src/HealthChecks/HealthChecks/test/DependencyInjection/HealthChecksBuilderTest.cs @@ -0,0 +1,257 @@ +// 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.Threading; +using System.Threading.Tasks; +using Microsoft.Extensions.Diagnostics.HealthChecks; +using Microsoft.Extensions.Options; +using Xunit; + +namespace Microsoft.Extensions.DependencyInjection +{ + // Integration tests for extension methods on IHealthCheckBuilder + // + // We test the longest overload of each 'family' of Add...Check methods, since they chain to each other. + public class HealthChecksBuilderTest + { + [Fact] + public void AddCheck_Instance() + { + // Arrange + var instance = new DelegateHealthCheck((_) => + { + return Task.FromResult(HealthCheckResult.Healthy()); + }); + + var services = CreateServices(); + services.AddHealthChecks().AddCheck("test", failureStatus: HealthStatus.Degraded,tags: new[] { "tag", }, instance: instance); + + var serviceProvider = services.BuildServiceProvider(); + + // Act + var options = serviceProvider.GetRequiredService>().Value; + + // Assert + var registration = Assert.Single(options.Registrations); + Assert.Equal("test", registration.Name); + Assert.Equal(HealthStatus.Degraded, registration.FailureStatus); + Assert.Equal(new[] { "tag", }, registration.Tags); + Assert.Same(instance, registration.Factory(serviceProvider)); + } + + [Fact] + public void AddCheck_T_TypeActivated() + { + // Arrange + var services = CreateServices(); + services.AddHealthChecks().AddCheck("test", failureStatus: HealthStatus.Degraded, tags: new[] { "tag", }); + + var serviceProvider = services.BuildServiceProvider(); + + // Act + var options = serviceProvider.GetRequiredService>().Value; + + // Assert + var registration = Assert.Single(options.Registrations); + Assert.Equal("test", registration.Name); + Assert.Equal(HealthStatus.Degraded, registration.FailureStatus); + Assert.Equal(new[] { "tag", }, registration.Tags); + Assert.IsType(registration.Factory(serviceProvider)); + } + + [Fact] + public void AddCheck_T_Service() + { + // Arrange + var instance = new TestHealthCheck(); + + var services = CreateServices(); + services.AddSingleton(instance); + services.AddHealthChecks().AddCheck("test", failureStatus: HealthStatus.Degraded, tags: new[] { "tag", }); + + var serviceProvider = services.BuildServiceProvider(); + + // Act + var options = serviceProvider.GetRequiredService>().Value; + + // Assert + var registration = Assert.Single(options.Registrations); + Assert.Equal("test", registration.Name); + Assert.Equal(HealthStatus.Degraded, registration.FailureStatus); + Assert.Equal(new[] { "tag", }, registration.Tags); + Assert.Same(instance, registration.Factory(serviceProvider)); + } + + [Fact] + public void AddTypeActivatedCheck() + { + // Arrange + var services = CreateServices(); + services + .AddHealthChecks() + .AddTypeActivatedCheck("test", failureStatus: HealthStatus.Degraded, tags: new[] { "tag", }, args: new object[] { 5, "hi", }); + + var serviceProvider = services.BuildServiceProvider(); + + // Act + var options = serviceProvider.GetRequiredService>().Value; + + // Assert + var registration = Assert.Single(options.Registrations); + Assert.Equal("test", registration.Name); + Assert.Equal(HealthStatus.Degraded, registration.FailureStatus); + Assert.Equal(new[] { "tag", }, registration.Tags); + + var check = Assert.IsType(registration.Factory(serviceProvider)); + Assert.Equal(5, check.I); + Assert.Equal("hi", check.S); + } + + [Fact] + public void AddDelegateCheck_NoArg() + { + // Arrange + var services = CreateServices(); + services.AddHealthChecks().AddCheck("test", tags: new[] { "tag", }, check: () => + { + return HealthCheckResult.Healthy(); + }); + + var serviceProvider = services.BuildServiceProvider(); + + // Act + var options = serviceProvider.GetRequiredService>().Value; + + // Assert + var registration = Assert.Single(options.Registrations); + Assert.Equal("test", registration.Name); + Assert.Equal(HealthStatus.Unhealthy, registration.FailureStatus); + Assert.Equal(new[] { "tag", }, registration.Tags); + Assert.IsType(registration.Factory(serviceProvider)); + } + + [Fact] + public void AddDelegateCheck_CancellationToken() + { + // Arrange + var services = CreateServices(); + services.AddHealthChecks().AddCheck("test", (_) => + { + return HealthCheckResult.Degraded(); + }, tags: new[] { "tag", }); + + var serviceProvider = services.BuildServiceProvider(); + + // Act + var options = serviceProvider.GetRequiredService>().Value; + + // Assert + var registration = Assert.Single(options.Registrations); + Assert.Equal("test", registration.Name); + Assert.Equal(HealthStatus.Unhealthy, registration.FailureStatus); + Assert.Equal(new[] { "tag", }, registration.Tags); + Assert.IsType(registration.Factory(serviceProvider)); + } + + [Fact] + public void AddAsyncDelegateCheck_NoArg() + { + // Arrange + var services = CreateServices(); + services.AddHealthChecks().AddAsyncCheck("test", () => + { + return Task.FromResult(HealthCheckResult.Healthy()); + }, tags: new[] { "tag", }); + + var serviceProvider = services.BuildServiceProvider(); + + // Act + var options = serviceProvider.GetRequiredService>().Value; + + // Assert + var registration = Assert.Single(options.Registrations); + Assert.Equal("test", registration.Name); + Assert.Equal(HealthStatus.Unhealthy, registration.FailureStatus); + Assert.Equal(new[] { "tag", }, registration.Tags); + Assert.IsType(registration.Factory(serviceProvider)); + } + + [Fact] + public void AddAsyncDelegateCheck_CancellationToken() + { + // Arrange + var services = CreateServices(); + services.AddHealthChecks().AddAsyncCheck("test", (_) => + { + return Task.FromResult(HealthCheckResult.Unhealthy()); + }, tags: new[] { "tag", }); + + var serviceProvider = services.BuildServiceProvider(); + + // Act + var options = serviceProvider.GetRequiredService>().Value; + + // Assert + var registration = Assert.Single(options.Registrations); + Assert.Equal("test", registration.Name); + Assert.Equal(HealthStatus.Unhealthy, registration.FailureStatus); + Assert.Equal(new[] { "tag", }, registration.Tags); + Assert.IsType(registration.Factory(serviceProvider)); + } + + [Fact] + public void ChecksCanBeRegisteredInMultipleCallsToAddHealthChecks() + { + var services = new ServiceCollection(); + services + .AddHealthChecks() + .AddAsyncCheck("Foo", () => Task.FromResult(HealthCheckResult.Healthy())); + services + .AddHealthChecks() + .AddAsyncCheck("Bar", () => Task.FromResult(HealthCheckResult.Healthy())); + + // Act + var options = services.BuildServiceProvider().GetRequiredService>(); + + // Assert + Assert.Collection( + options.Value.Registrations, + actual => Assert.Equal("Foo", actual.Name), + actual => Assert.Equal("Bar", actual.Name)); + } + + private IServiceCollection CreateServices() + { + var services = new ServiceCollection(); + services.AddLogging(); + services.AddOptions(); + return services; + } + + private class TestHealthCheck : IHealthCheck + { + public Task CheckHealthAsync(HealthCheckContext context, CancellationToken cancellationToken = default) + { + throw new System.NotImplementedException(); + } + } + + private class TestHealthCheckWithArgs : IHealthCheck + { + public TestHealthCheckWithArgs(int i, string s) + { + I = i; + S = s; + } + + public int I { get; set; } + + public string S { get; set; } + + public Task CheckHealthAsync(HealthCheckContext context, CancellationToken cancellationToken = default) + { + throw new System.NotImplementedException(); + } + } + } +} diff --git a/src/HealthChecks/HealthChecks/test/DependencyInjection/ServiceCollectionExtensionsTest.cs b/src/HealthChecks/HealthChecks/test/DependencyInjection/ServiceCollectionExtensionsTest.cs new file mode 100644 index 0000000000..694a97628d --- /dev/null +++ b/src/HealthChecks/HealthChecks/test/DependencyInjection/ServiceCollectionExtensionsTest.cs @@ -0,0 +1,43 @@ +// 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.Linq; +using Microsoft.Extensions.Diagnostics.HealthChecks; +using Microsoft.Extensions.Hosting; +using Xunit; + +namespace Microsoft.Extensions.DependencyInjection +{ + public class ServiceCollectionExtensionsTest + { + [Fact] + public void AddHealthChecks_RegistersSingletonHealthCheckServiceIdempotently() + { + // Arrange + var services = new ServiceCollection(); + + // Act + services.AddHealthChecks(); + services.AddHealthChecks(); + + // Assert + Assert.Collection(services.OrderBy(s => s.ServiceType.FullName), + actual => + { + Assert.Equal(ServiceLifetime.Singleton, actual.Lifetime); + Assert.Equal(typeof(HealthCheckService), actual.ServiceType); + Assert.Equal(typeof(DefaultHealthCheckService), actual.ImplementationType); + Assert.Null(actual.ImplementationInstance); + Assert.Null(actual.ImplementationFactory); + }, + actual => + { + Assert.Equal(ServiceLifetime.Singleton, actual.Lifetime); + Assert.Equal(typeof(IHostedService), actual.ServiceType); + Assert.Equal(typeof(HealthCheckPublisherHostedService), actual.ImplementationType); + Assert.Null(actual.ImplementationInstance); + Assert.Null(actual.ImplementationFactory); + }); + } + } +} diff --git a/src/HealthChecks/HealthChecks/test/HealthCheckPublisherHostedServiceTest.cs b/src/HealthChecks/HealthChecks/test/HealthCheckPublisherHostedServiceTest.cs new file mode 100644 index 0000000000..94687efcb8 --- /dev/null +++ b/src/HealthChecks/HealthChecks/test/HealthCheckPublisherHostedServiceTest.cs @@ -0,0 +1,528 @@ +// 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.Collections.Generic; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Testing; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Hosting; +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Logging.Testing; +using Xunit; + +namespace Microsoft.Extensions.Diagnostics.HealthChecks +{ + public class HealthCheckPublisherHostedServiceTest + { + [Fact] + public async Task StartAsync_WithoutPublishers_DoesNotStartTimer() + { + // Arrange + var publishers = new IHealthCheckPublisher[] + { + }; + + var service = CreateService(publishers); + + try + { + // Act + await service.StartAsync(); + + // Assert + Assert.False(service.IsTimerRunning); + Assert.False(service.IsStopping); + } + finally + { + await service.StopAsync(); + Assert.False(service.IsTimerRunning); + Assert.True(service.IsStopping); + } + } + + [Fact] + public async Task StartAsync_WithPublishers_StartsTimer() + { + // Arrange + var publishers = new IHealthCheckPublisher[] + { + new TestPublisher(), + }; + + var service = CreateService(publishers); + + try + { + // Act + await service.StartAsync(); + + // Assert + Assert.True(service.IsTimerRunning); + Assert.False(service.IsStopping); + } + finally + { + await service.StopAsync(); + Assert.False(service.IsTimerRunning); + Assert.True(service.IsStopping); + } + } + + [Fact] + public async Task StartAsync_WithPublishers_StartsTimer_RunsPublishers() + { + // Arrange + var unblock0 = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); + var unblock1 = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); + var unblock2 = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); + + var publishers = new TestPublisher[] + { + new TestPublisher() { Wait = unblock0.Task, }, + new TestPublisher() { Wait = unblock1.Task, }, + new TestPublisher() { Wait = unblock2.Task, }, + }; + + var service = CreateService(publishers, configure: (options) => + { + options.Delay = TimeSpan.FromMilliseconds(0); + }); + + try + { + // Act + await service.StartAsync(); + + await publishers[0].Started.TimeoutAfter(TimeSpan.FromSeconds(10)); + await publishers[1].Started.TimeoutAfter(TimeSpan.FromSeconds(10)); + await publishers[2].Started.TimeoutAfter(TimeSpan.FromSeconds(10)); + + unblock0.SetResult(null); + unblock1.SetResult(null); + unblock2.SetResult(null); + + // Assert + Assert.True(service.IsTimerRunning); + Assert.False(service.IsStopping); + } + finally + { + await service.StopAsync(); + Assert.False(service.IsTimerRunning); + Assert.True(service.IsStopping); + } + } + + [Fact] + public async Task StopAsync_CancelsExecution() + { + // Arrange + var unblock = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); + + var publishers = new TestPublisher[] + { + new TestPublisher() { Wait = unblock.Task, } + }; + + var service = CreateService(publishers); + + try + { + await service.StartAsync(); + + // Start execution + var running = service.RunAsync(); + + // Wait for the publisher to see the cancellation token + await publishers[0].Started.TimeoutAfter(TimeSpan.FromSeconds(10)); + Assert.Single(publishers[0].Entries); + + // Act + await service.StopAsync(); // Trigger cancellation + + // Assert + await AssertCancelledAsync(publishers[0].Entries[0].cancellationToken); + Assert.False(service.IsTimerRunning); + Assert.True(service.IsStopping); + + unblock.SetResult(null); + + await running.TimeoutAfter(TimeSpan.FromSeconds(10)); + } + finally + { + await service.StopAsync(); + Assert.False(service.IsTimerRunning); + Assert.True(service.IsStopping); + } + } + + [Fact] + public async Task RunAsync_WaitsForCompletion_Single() + { + // Arrange + var sink = new TestSink(); + + var unblock = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); + + var publishers = new TestPublisher[] + { + new TestPublisher() { Wait = unblock.Task, }, + }; + + var service = CreateService(publishers, sink: sink); + + try + { + await service.StartAsync(); + + // Act + var running = service.RunAsync(); + + await publishers[0].Started.TimeoutAfter(TimeSpan.FromSeconds(10)); + + unblock.SetResult(null); + + await running.TimeoutAfter(TimeSpan.FromSeconds(10)); + + // Assert + Assert.True(service.IsTimerRunning); + Assert.False(service.IsStopping); + + for (var i = 0; i < publishers.Length; i++) + { + var report = Assert.Single(publishers[i].Entries).report; + Assert.Equal(new[] { "one", "two", }, report.Entries.Keys.OrderBy(k => k)); + } + } + finally + { + await service.StopAsync(); + Assert.False(service.IsTimerRunning); + Assert.True(service.IsStopping); + } + + Assert.Collection( + sink.Writes, + entry => { Assert.Equal(HealthCheckPublisherHostedService.EventIds.HealthCheckPublisherProcessingBegin, entry.EventId); }, + entry => { Assert.Equal(DefaultHealthCheckService.EventIds.HealthCheckProcessingBegin, entry.EventId); }, + entry => { Assert.Equal(DefaultHealthCheckService.EventIds.HealthCheckBegin, entry.EventId); }, + entry => { Assert.Equal(DefaultHealthCheckService.EventIds.HealthCheckEnd, entry.EventId); }, + entry => { Assert.Equal(DefaultHealthCheckService.EventIds.HealthCheckBegin, entry.EventId); }, + entry => { Assert.Equal(DefaultHealthCheckService.EventIds.HealthCheckEnd, entry.EventId); }, + entry => { Assert.Equal(DefaultHealthCheckService.EventIds.HealthCheckProcessingEnd, entry.EventId); }, + entry => { Assert.Equal(HealthCheckPublisherHostedService.EventIds.HealthCheckPublisherBegin, entry.EventId); }, + entry => { Assert.Equal(HealthCheckPublisherHostedService.EventIds.HealthCheckPublisherEnd, entry.EventId); }, + entry => { Assert.Equal(HealthCheckPublisherHostedService.EventIds.HealthCheckPublisherProcessingEnd, entry.EventId); }); + } + + // Not testing logs here to avoid differences in logging order + [Fact] + public async Task RunAsync_WaitsForCompletion_Multiple() + { + // Arrange + var unblock0 = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); + var unblock1 = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); + var unblock2 = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); + + var publishers = new TestPublisher[] + { + new TestPublisher() { Wait = unblock0.Task, }, + new TestPublisher() { Wait = unblock1.Task, }, + new TestPublisher() { Wait = unblock2.Task, }, + }; + + var service = CreateService(publishers); + + try + { + await service.StartAsync(); + + // Act + var running = service.RunAsync(); + + await publishers[0].Started.TimeoutAfter(TimeSpan.FromSeconds(10)); + await publishers[1].Started.TimeoutAfter(TimeSpan.FromSeconds(10)); + await publishers[2].Started.TimeoutAfter(TimeSpan.FromSeconds(10)); + + unblock0.SetResult(null); + unblock1.SetResult(null); + unblock2.SetResult(null); + + await running.TimeoutAfter(TimeSpan.FromSeconds(10)); + + // Assert + Assert.True(service.IsTimerRunning); + Assert.False(service.IsStopping); + + for (var i = 0; i < publishers.Length; i++) + { + var report = Assert.Single(publishers[i].Entries).report; + Assert.Equal(new[] { "one", "two", }, report.Entries.Keys.OrderBy(k => k)); + } + } + finally + { + await service.StopAsync(); + Assert.False(service.IsTimerRunning); + Assert.True(service.IsStopping); + } + } + + [Fact] + public async Task RunAsync_PublishersCanTimeout() + { + // Arrange + var sink = new TestSink(); + var unblock = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); + + var publishers = new TestPublisher[] + { + new TestPublisher() { Wait = unblock.Task, }, + }; + + var service = CreateService(publishers, sink: sink, configure: (options) => + { + options.Timeout = TimeSpan.FromMilliseconds(50); + }); + + try + { + await service.StartAsync(); + + // Act + var running = service.RunAsync(); + + await publishers[0].Started.TimeoutAfter(TimeSpan.FromSeconds(10)); + + await AssertCancelledAsync(publishers[0].Entries[0].cancellationToken); + + unblock.SetResult(null); + + await running.TimeoutAfter(TimeSpan.FromSeconds(10)); + + // Assert + Assert.True(service.IsTimerRunning); + Assert.False(service.IsStopping); + } + finally + { + await service.StopAsync(); + Assert.False(service.IsTimerRunning); + Assert.True(service.IsStopping); + } + + Assert.Collection( + sink.Writes, + entry => { Assert.Equal(HealthCheckPublisherHostedService.EventIds.HealthCheckPublisherProcessingBegin, entry.EventId); }, + entry => { Assert.Equal(DefaultHealthCheckService.EventIds.HealthCheckProcessingBegin, entry.EventId); }, + entry => { Assert.Equal(DefaultHealthCheckService.EventIds.HealthCheckBegin, entry.EventId); }, + entry => { Assert.Equal(DefaultHealthCheckService.EventIds.HealthCheckEnd, entry.EventId); }, + entry => { Assert.Equal(DefaultHealthCheckService.EventIds.HealthCheckBegin, entry.EventId); }, + entry => { Assert.Equal(DefaultHealthCheckService.EventIds.HealthCheckEnd, entry.EventId); }, + entry => { Assert.Equal(DefaultHealthCheckService.EventIds.HealthCheckProcessingEnd, entry.EventId); }, + entry => { Assert.Equal(HealthCheckPublisherHostedService.EventIds.HealthCheckPublisherBegin, entry.EventId); }, + entry => { Assert.Equal(HealthCheckPublisherHostedService.EventIds.HealthCheckPublisherTimeout, entry.EventId); }, + entry => { Assert.Equal(HealthCheckPublisherHostedService.EventIds.HealthCheckPublisherProcessingEnd, entry.EventId); }); + } + + [Fact] + public async Task RunAsync_CanFilterHealthChecks() + { + // Arrange + var publishers = new TestPublisher[] + { + new TestPublisher(), + new TestPublisher(), + }; + + var service = CreateService(publishers, configure: (options) => + { + options.Predicate = (r) => r.Name == "one"; + }); + + try + { + await service.StartAsync(); + + // Act + await service.RunAsync().TimeoutAfter(TimeSpan.FromSeconds(10)); + + // Assert + for (var i = 0; i < publishers.Length; i++) + { + var report = Assert.Single(publishers[i].Entries).report; + Assert.Equal(new[] { "one", }, report.Entries.Keys.OrderBy(k => k)); + } + } + finally + { + await service.StopAsync(); + Assert.False(service.IsTimerRunning); + Assert.True(service.IsStopping); + } + } + + [Fact] + public async Task RunAsync_HandlesExceptions() + { + // Arrange + var sink = new TestSink(); + var publishers = new TestPublisher[] + { + new TestPublisher() { Exception = new InvalidTimeZoneException(), }, + }; + + var service = CreateService(publishers, sink: sink); + + try + { + await service.StartAsync(); + + // Act + await service.RunAsync().TimeoutAfter(TimeSpan.FromSeconds(10)); + + } + finally + { + await service.StopAsync(); + Assert.False(service.IsTimerRunning); + Assert.True(service.IsStopping); + } + + Assert.Collection( + sink.Writes, + entry => { Assert.Equal(HealthCheckPublisherHostedService.EventIds.HealthCheckPublisherProcessingBegin, entry.EventId); }, + entry => { Assert.Equal(DefaultHealthCheckService.EventIds.HealthCheckProcessingBegin, entry.EventId); }, + entry => { Assert.Equal(DefaultHealthCheckService.EventIds.HealthCheckBegin, entry.EventId); }, + entry => { Assert.Equal(DefaultHealthCheckService.EventIds.HealthCheckEnd, entry.EventId); }, + entry => { Assert.Equal(DefaultHealthCheckService.EventIds.HealthCheckBegin, entry.EventId); }, + entry => { Assert.Equal(DefaultHealthCheckService.EventIds.HealthCheckEnd, entry.EventId); }, + entry => { Assert.Equal(DefaultHealthCheckService.EventIds.HealthCheckProcessingEnd, entry.EventId); }, + entry => { Assert.Equal(HealthCheckPublisherHostedService.EventIds.HealthCheckPublisherBegin, entry.EventId); }, + entry => { Assert.Equal(HealthCheckPublisherHostedService.EventIds.HealthCheckPublisherError, entry.EventId); }, + entry => { Assert.Equal(HealthCheckPublisherHostedService.EventIds.HealthCheckPublisherProcessingEnd, entry.EventId); }); + } + + // Not testing logging here to avoid flaky ordering issues + [Fact] + public async Task RunAsync_HandlesExceptions_Multiple() + { + // Arrange + var sink = new TestSink(); + var publishers = new TestPublisher[] + { + new TestPublisher() { Exception = new InvalidTimeZoneException(), }, + new TestPublisher(), + new TestPublisher() { Exception = new InvalidTimeZoneException(), }, + }; + + var service = CreateService(publishers, sink: sink); + + try + { + await service.StartAsync(); + + // Act + await service.RunAsync().TimeoutAfter(TimeSpan.FromSeconds(10)); + + } + finally + { + await service.StopAsync(); + Assert.False(service.IsTimerRunning); + Assert.True(service.IsStopping); + } + } + + private HealthCheckPublisherHostedService CreateService( + IHealthCheckPublisher[] publishers, + Action configure = null, + TestSink sink = null) + { + var serviceCollection = new ServiceCollection(); + serviceCollection.AddOptions(); + serviceCollection.AddLogging(); + serviceCollection.AddHealthChecks() + .AddCheck("one", () => { return HealthCheckResult.Healthy(); }) + .AddCheck("two", () => { return HealthCheckResult.Healthy(); }); + + // Choosing big values for tests to make sure that we're not dependent on the defaults. + // All of the tests that rely on the timer will set their own values for speed. + serviceCollection.Configure(options => + { + options.Delay = TimeSpan.FromMinutes(5); + options.Period = TimeSpan.FromMinutes(5); + options.Timeout = TimeSpan.FromMinutes(5); + }); + + if (publishers != null) + { + for (var i = 0; i < publishers.Length; i++) + { + serviceCollection.AddSingleton(publishers[i]); + } + } + + if (configure != null) + { + serviceCollection.Configure(configure); + } + + if (sink != null) + { + serviceCollection.AddSingleton(new TestLoggerFactory(sink, enabled: true)); + } + + var services = serviceCollection.BuildServiceProvider(); + return services.GetServices().OfType< HealthCheckPublisherHostedService>().Single(); + } + + private static async Task AssertCancelledAsync(CancellationToken cancellationToken) + { + await Assert.ThrowsAsync(() => Task.Delay(TimeSpan.FromSeconds(10), cancellationToken)); + } + + private class TestPublisher : IHealthCheckPublisher + { + private TaskCompletionSource _started; + + public TestPublisher() + { + _started = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); + } + + public List<(HealthReport report, CancellationToken cancellationToken)> Entries { get; } = new List<(HealthReport report, CancellationToken cancellationToken)>(); + + public Exception Exception { get; set; } + + public Task Started => _started.Task; + + public Task Wait { get; set; } + + public async Task PublishAsync(HealthReport report, CancellationToken cancellationToken) + { + Entries.Add((report, cancellationToken)); + + // Signal that we've started + _started.SetResult(null); + + if (Wait != null) + { + await Wait; + } + + if (Exception != null) + { + throw Exception; + } + + cancellationToken.ThrowIfCancellationRequested(); + } + } + } +} diff --git a/src/HealthChecks/HealthChecks/test/HealthReportTest.cs b/src/HealthChecks/HealthChecks/test/HealthReportTest.cs new file mode 100644 index 0000000000..07f8e5a8e3 --- /dev/null +++ b/src/HealthChecks/HealthChecks/test/HealthReportTest.cs @@ -0,0 +1,45 @@ +// 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.Collections.Generic; +using Xunit; + +namespace Microsoft.Extensions.Diagnostics.HealthChecks +{ + public class HealthReportTest + { + [Theory] + [InlineData(HealthStatus.Healthy)] + [InlineData(HealthStatus.Degraded)] + [InlineData(HealthStatus.Unhealthy)] + public void Status_MatchesWorstStatusInResults(HealthStatus status) + { + var result = new HealthReport(new Dictionary() + { + {"Foo", new HealthReportEntry(HealthStatus.Healthy, null,TimeSpan.MinValue, null, null) }, + {"Bar", new HealthReportEntry(HealthStatus.Healthy, null, TimeSpan.MinValue,null, null) }, + {"Baz", new HealthReportEntry(status, exception: null, description: null,duration:TimeSpan.MinValue, data: null) }, + {"Quick", new HealthReportEntry(HealthStatus.Healthy, null, TimeSpan.MinValue, null, null) }, + {"Quack", new HealthReportEntry(HealthStatus.Healthy, null, TimeSpan.MinValue, null, null) }, + {"Quock", new HealthReportEntry(HealthStatus.Healthy, null, TimeSpan.MinValue, null, null) }, + }, totalDuration: TimeSpan.MinValue); + + Assert.Equal(status, result.Status); + } + + [Theory] + [InlineData(200)] + [InlineData(300)] + [InlineData(400)] + public void TotalDuration_MatchesTotalDurationParameter(int milliseconds) + { + var result = new HealthReport(new Dictionary() + { + {"Foo", new HealthReportEntry(HealthStatus.Healthy, null,TimeSpan.MinValue, null, null) } + }, totalDuration: TimeSpan.FromMilliseconds(milliseconds)); + + Assert.Equal(TimeSpan.FromMilliseconds(milliseconds), result.TotalDuration); + } + } +} diff --git a/src/HealthChecks/HealthChecks/test/Microsoft.Extensions.Diagnostics.HealthChecks.Tests.csproj b/src/HealthChecks/HealthChecks/test/Microsoft.Extensions.Diagnostics.HealthChecks.Tests.csproj new file mode 100644 index 0000000000..e822f6a7a0 --- /dev/null +++ b/src/HealthChecks/HealthChecks/test/Microsoft.Extensions.Diagnostics.HealthChecks.Tests.csproj @@ -0,0 +1,12 @@ + + + + $(StandardTestTfms) + Microsoft.Extensions.Diagnostics.HealthChecks + + + + + + + From e5fee282abf55ea72729256f7e532d0aa7b226ba Mon Sep 17 00:00:00 2001 From: Nate McMaster Date: Mon, 3 Dec 2018 16:17:41 -0800 Subject: [PATCH 0022/1101] Reorganize source code in preparation to move into aspnet/Extensions Prior to reorganization, this source code was found in https://github.com/aspnet/Localization/tree/dotnet/extensions@0bcac31dd705fb9db60723f5d7eaeffb728358f5 \n\nCommit migrated from https://github.com/dotnet/extensions/commit/c8e0450d7a0b8310f671c0c39bc19638b7b04c47 --- .../Abstractions/src/IStringLocalizer.cs | 45 ++ .../src/IStringLocalizerFactory.cs | 29 + .../Abstractions/src/IStringLocalizerOfT.cs | 14 + .../Abstractions/src/LocalizedString.cs | 90 +++ ...xtensions.Localization.Abstractions.csproj | 15 + .../src/StringLocalizerExtensions.cs | 74 ++ .../Abstractions/src/StringLocalizerOfT.cs | 67 ++ .../Abstractions/src/baseline.netcore.json | 413 +++++++++++ .../Localization/src/IResourceNamesCache.cs | 22 + .../src/Internal/AssemblyWrapper.cs | 28 + .../src/Internal/IResourceStringProvider.cs | 13 + ...eManagerStringLocalizerLoggerExtensions.cs | 27 + .../Internal/ResourceManagerStringProvider.cs | 80 ++ .../Localization/src/LocalizationOptions.cs | 16 + ...LocalizationServiceCollectionExtensions.cs | 76 ++ .../Microsoft.Extensions.Localization.csproj | 19 + .../src/Properties/AssemblyInfo.cs | 6 + .../src/Properties/Resources.Designer.cs | 62 ++ .../src/ResourceLocationAttribute.cs | 33 + .../src/ResourceManagerStringLocalizer.cs | 274 +++++++ .../ResourceManagerStringLocalizerFactory.cs | 270 +++++++ ...sourceManagerWithCultureStringLocalizer.cs | 164 +++++ .../Localization/src/ResourceNamesCache.cs | 23 + .../Localization/src/Resources.resx | 126 ++++ .../src/RootNamespaceAttribute.cs | 35 + .../Localization/src/baseline.netcore.json | 687 ++++++++++++++++++ ...lizationServiceCollectionExtensionsTest.cs | 68 ++ ...osoft.Extensions.Localization.Tests.csproj | 14 + ...sourceManagerStringLocalizerFactoryTest.cs | 296 ++++++++ .../ResourceManagerStringLocalizerTest.cs | 299 ++++++++ 30 files changed, 3385 insertions(+) create mode 100644 src/Localization/Abstractions/src/IStringLocalizer.cs create mode 100644 src/Localization/Abstractions/src/IStringLocalizerFactory.cs create mode 100644 src/Localization/Abstractions/src/IStringLocalizerOfT.cs create mode 100644 src/Localization/Abstractions/src/LocalizedString.cs create mode 100644 src/Localization/Abstractions/src/Microsoft.Extensions.Localization.Abstractions.csproj create mode 100644 src/Localization/Abstractions/src/StringLocalizerExtensions.cs create mode 100644 src/Localization/Abstractions/src/StringLocalizerOfT.cs create mode 100644 src/Localization/Abstractions/src/baseline.netcore.json create mode 100644 src/Localization/Localization/src/IResourceNamesCache.cs create mode 100644 src/Localization/Localization/src/Internal/AssemblyWrapper.cs create mode 100644 src/Localization/Localization/src/Internal/IResourceStringProvider.cs create mode 100644 src/Localization/Localization/src/Internal/ResourceManagerStringLocalizerLoggerExtensions.cs create mode 100644 src/Localization/Localization/src/Internal/ResourceManagerStringProvider.cs create mode 100644 src/Localization/Localization/src/LocalizationOptions.cs create mode 100644 src/Localization/Localization/src/LocalizationServiceCollectionExtensions.cs create mode 100644 src/Localization/Localization/src/Microsoft.Extensions.Localization.csproj create mode 100644 src/Localization/Localization/src/Properties/AssemblyInfo.cs create mode 100644 src/Localization/Localization/src/Properties/Resources.Designer.cs create mode 100644 src/Localization/Localization/src/ResourceLocationAttribute.cs create mode 100644 src/Localization/Localization/src/ResourceManagerStringLocalizer.cs create mode 100644 src/Localization/Localization/src/ResourceManagerStringLocalizerFactory.cs create mode 100644 src/Localization/Localization/src/ResourceManagerWithCultureStringLocalizer.cs create mode 100644 src/Localization/Localization/src/ResourceNamesCache.cs create mode 100644 src/Localization/Localization/src/Resources.resx create mode 100644 src/Localization/Localization/src/RootNamespaceAttribute.cs create mode 100644 src/Localization/Localization/src/baseline.netcore.json create mode 100644 src/Localization/Localization/test/LocalizationServiceCollectionExtensionsTest.cs create mode 100644 src/Localization/Localization/test/Microsoft.Extensions.Localization.Tests.csproj create mode 100644 src/Localization/Localization/test/ResourceManagerStringLocalizerFactoryTest.cs create mode 100644 src/Localization/Localization/test/ResourceManagerStringLocalizerTest.cs diff --git a/src/Localization/Abstractions/src/IStringLocalizer.cs b/src/Localization/Abstractions/src/IStringLocalizer.cs new file mode 100644 index 0000000000..0e1145bbca --- /dev/null +++ b/src/Localization/Abstractions/src/IStringLocalizer.cs @@ -0,0 +1,45 @@ +// 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.Collections.Generic; +using System.Globalization; + +namespace Microsoft.Extensions.Localization +{ + /// + /// Represents a service that provides localized strings. + /// + public interface IStringLocalizer + { + /// + /// Gets the string resource with the given name. + /// + /// The name of the string resource. + /// The string resource as a . + LocalizedString this[string name] { get; } + + /// + /// Gets the string resource with the given name and formatted with the supplied arguments. + /// + /// The name of the string resource. + /// The values to format the string with. + /// The formatted string resource as a . + LocalizedString this[string name, params object[] arguments] { get; } + + /// + /// Gets all string resources. + /// + /// + /// A indicating whether to include strings from parent cultures. + /// + /// The strings. + IEnumerable GetAllStrings(bool includeParentCultures); + + /// + /// Creates a new for a specific . + /// + /// The to use. + /// A culture-specific . + IStringLocalizer WithCulture(CultureInfo culture); + } +} \ No newline at end of file diff --git a/src/Localization/Abstractions/src/IStringLocalizerFactory.cs b/src/Localization/Abstractions/src/IStringLocalizerFactory.cs new file mode 100644 index 0000000000..559fa69c30 --- /dev/null +++ b/src/Localization/Abstractions/src/IStringLocalizerFactory.cs @@ -0,0 +1,29 @@ +// 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; + +namespace Microsoft.Extensions.Localization +{ + /// + /// Represents a factory that creates instances. + /// + public interface IStringLocalizerFactory + { + /// + /// Creates an using the and + /// of the specified . + /// + /// The . + /// The . + IStringLocalizer Create(Type resourceSource); + + /// + /// Creates an . + /// + /// The base name of the resource to load strings from. + /// The location to load resources from. + /// The . + IStringLocalizer Create(string baseName, string location); + } +} \ No newline at end of file diff --git a/src/Localization/Abstractions/src/IStringLocalizerOfT.cs b/src/Localization/Abstractions/src/IStringLocalizerOfT.cs new file mode 100644 index 0000000000..695678a900 --- /dev/null +++ b/src/Localization/Abstractions/src/IStringLocalizerOfT.cs @@ -0,0 +1,14 @@ +// 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. + +namespace Microsoft.Extensions.Localization +{ + /// + /// Represents an that provides strings for . + /// + /// The to provide strings for. + public interface IStringLocalizer : IStringLocalizer + { + + } +} \ No newline at end of file diff --git a/src/Localization/Abstractions/src/LocalizedString.cs b/src/Localization/Abstractions/src/LocalizedString.cs new file mode 100644 index 0000000000..6556da40a0 --- /dev/null +++ b/src/Localization/Abstractions/src/LocalizedString.cs @@ -0,0 +1,90 @@ +// 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; + +namespace Microsoft.Extensions.Localization +{ + /// + /// A locale specific string. + /// + public class LocalizedString + { + /// + /// Creates a new . + /// + /// The name of the string in the resource it was loaded from. + /// The actual string. + public LocalizedString(string name, string value) + : this(name, value, resourceNotFound: false) + { + } + + /// + /// Creates a new . + /// + /// The name of the string in the resource it was loaded from. + /// The actual string. + /// Whether the string was not found in a resource. Set this to true to indicate an alternate string value was used. + public LocalizedString(string name, string value, bool resourceNotFound) + : this(name, value, resourceNotFound, searchedLocation: null) + { + } + + /// + /// Creates a new . + /// + /// The name of the string in the resource it was loaded from. + /// The actual string. + /// Whether the string was not found in a resource. Set this to true to indicate an alternate string value was used. + /// The location which was searched for a localization value. + public LocalizedString(string name, string value, bool resourceNotFound, string searchedLocation) + { + if (name == null) + { + throw new ArgumentNullException(nameof(name)); + } + + if (value == null) + { + throw new ArgumentNullException(nameof(value)); + } + + Name = name; + Value = value; + ResourceNotFound = resourceNotFound; + SearchedLocation = searchedLocation; + } + + public static implicit operator string(LocalizedString localizedString) + { + return localizedString?.Value; + } + + /// + /// The name of the string in the resource it was loaded from. + /// + public string Name { get; } + + /// + /// The actual string. + /// + public string Value { get; } + + /// + /// Whether the string was not found in a resource. If true, an alternate string value was used. + /// + public bool ResourceNotFound { get; } + + /// + /// The location which was searched for a localization value. + /// + public string SearchedLocation { get; } + + /// + /// Returns the actual string. + /// + /// The actual string. + public override string ToString() => Value; + } +} \ No newline at end of file diff --git a/src/Localization/Abstractions/src/Microsoft.Extensions.Localization.Abstractions.csproj b/src/Localization/Abstractions/src/Microsoft.Extensions.Localization.Abstractions.csproj new file mode 100644 index 0000000000..8508eb071a --- /dev/null +++ b/src/Localization/Abstractions/src/Microsoft.Extensions.Localization.Abstractions.csproj @@ -0,0 +1,15 @@ + + + + Microsoft .NET Extensions + Abstractions of application localization services. +Commonly used types: +Microsoft.Extensions.Localization.IStringLocalizer +Microsoft.Extensions.Localization.IStringLocalizer<T> + netstandard2.0 + $(NoWarn);CS1591 + true + localization + + + diff --git a/src/Localization/Abstractions/src/StringLocalizerExtensions.cs b/src/Localization/Abstractions/src/StringLocalizerExtensions.cs new file mode 100644 index 0000000000..bde47f74f3 --- /dev/null +++ b/src/Localization/Abstractions/src/StringLocalizerExtensions.cs @@ -0,0 +1,74 @@ +// 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.Collections.Generic; + +namespace Microsoft.Extensions.Localization +{ + public static class StringLocalizerExtensions + { + /// + /// Gets the string resource with the given name. + /// + /// The . + /// The name of the string resource. + /// The string resource as a . + public static LocalizedString GetString( + this IStringLocalizer stringLocalizer, + string name) + { + if (stringLocalizer == null) + { + throw new ArgumentNullException(nameof(stringLocalizer)); + } + + if (name == null) + { + throw new ArgumentNullException(nameof(name)); + } + + return stringLocalizer[name]; + } + + /// + /// Gets the string resource with the given name and formatted with the supplied arguments. + /// + /// The . + /// The name of the string resource. + /// The values to format the string with. + /// The formatted string resource as a . + public static LocalizedString GetString( + this IStringLocalizer stringLocalizer, + string name, + params object[] arguments) + { + if (stringLocalizer == null) + { + throw new ArgumentNullException(nameof(stringLocalizer)); + } + + if (name == null) + { + throw new ArgumentNullException(nameof(name)); + } + + return stringLocalizer[name, arguments]; + } + + /// + /// Gets all string resources including those for parent cultures. + /// + /// The . + /// The string resources. + public static IEnumerable GetAllStrings(this IStringLocalizer stringLocalizer) + { + if (stringLocalizer == null) + { + throw new ArgumentNullException(nameof(stringLocalizer)); + } + + return stringLocalizer.GetAllStrings(includeParentCultures: true); + } + } +} diff --git a/src/Localization/Abstractions/src/StringLocalizerOfT.cs b/src/Localization/Abstractions/src/StringLocalizerOfT.cs new file mode 100644 index 0000000000..131c1126ec --- /dev/null +++ b/src/Localization/Abstractions/src/StringLocalizerOfT.cs @@ -0,0 +1,67 @@ +// 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.Collections.Generic; +using System.Globalization; + +namespace Microsoft.Extensions.Localization +{ + /// + /// Provides strings for . + /// + /// The to provide strings for. + public class StringLocalizer : IStringLocalizer + { + private IStringLocalizer _localizer; + + /// + /// Creates a new . + /// + /// The to use. + public StringLocalizer(IStringLocalizerFactory factory) + { + if (factory == null) + { + throw new ArgumentNullException(nameof(factory)); + } + + _localizer = factory.Create(typeof(TResourceSource)); + } + + /// + public virtual IStringLocalizer WithCulture(CultureInfo culture) => _localizer.WithCulture(culture); + + /// + public virtual LocalizedString this[string name] + { + get + { + if (name == null) + { + throw new ArgumentNullException(nameof(name)); + } + + return _localizer[name]; + } + } + + /// + public virtual LocalizedString this[string name, params object[] arguments] + { + get + { + if (name == null) + { + throw new ArgumentNullException(nameof(name)); + } + + return _localizer[name, arguments]; + } + } + + /// + public IEnumerable GetAllStrings(bool includeParentCultures) => + _localizer.GetAllStrings(includeParentCultures); + } +} \ No newline at end of file diff --git a/src/Localization/Abstractions/src/baseline.netcore.json b/src/Localization/Abstractions/src/baseline.netcore.json new file mode 100644 index 0000000000..02ba71db8e --- /dev/null +++ b/src/Localization/Abstractions/src/baseline.netcore.json @@ -0,0 +1,413 @@ +{ + "AssemblyIdentity": "Microsoft.Extensions.Localization.Abstractions, Version=2.1.1.0, Culture=neutral, PublicKeyToken=adb9793829ddae60", + "Types": [ + { + "Name": "Microsoft.Extensions.Localization.IStringLocalizer", + "Visibility": "Public", + "Kind": "Interface", + "Abstract": true, + "ImplementedInterfaces": [], + "Members": [ + { + "Kind": "Method", + "Name": "get_Item", + "Parameters": [ + { + "Name": "name", + "Type": "System.String" + } + ], + "ReturnType": "Microsoft.Extensions.Localization.LocalizedString", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "get_Item", + "Parameters": [ + { + "Name": "name", + "Type": "System.String" + }, + { + "Name": "arguments", + "Type": "System.Object[]", + "IsParams": true + } + ], + "ReturnType": "Microsoft.Extensions.Localization.LocalizedString", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "GetAllStrings", + "Parameters": [ + { + "Name": "includeParentCultures", + "Type": "System.Boolean" + } + ], + "ReturnType": "System.Collections.Generic.IEnumerable", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "WithCulture", + "Parameters": [ + { + "Name": "culture", + "Type": "System.Globalization.CultureInfo" + } + ], + "ReturnType": "Microsoft.Extensions.Localization.IStringLocalizer", + "GenericParameter": [] + } + ], + "GenericParameters": [] + }, + { + "Name": "Microsoft.Extensions.Localization.IStringLocalizerFactory", + "Visibility": "Public", + "Kind": "Interface", + "Abstract": true, + "ImplementedInterfaces": [], + "Members": [ + { + "Kind": "Method", + "Name": "Create", + "Parameters": [ + { + "Name": "resourceSource", + "Type": "System.Type" + } + ], + "ReturnType": "Microsoft.Extensions.Localization.IStringLocalizer", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "Create", + "Parameters": [ + { + "Name": "baseName", + "Type": "System.String" + }, + { + "Name": "location", + "Type": "System.String" + } + ], + "ReturnType": "Microsoft.Extensions.Localization.IStringLocalizer", + "GenericParameter": [] + } + ], + "GenericParameters": [] + }, + { + "Name": "Microsoft.Extensions.Localization.IStringLocalizer", + "Visibility": "Public", + "Kind": "Interface", + "Abstract": true, + "ImplementedInterfaces": [ + "Microsoft.Extensions.Localization.IStringLocalizer" + ], + "Members": [], + "GenericParameters": [ + { + "ParameterName": "T", + "ParameterPosition": 0, + "BaseTypeOrInterfaces": [] + } + ] + }, + { + "Name": "Microsoft.Extensions.Localization.LocalizedString", + "Visibility": "Public", + "Kind": "Class", + "ImplementedInterfaces": [], + "Members": [ + { + "Kind": "Method", + "Name": "op_Implicit", + "Parameters": [ + { + "Name": "localizedString", + "Type": "Microsoft.Extensions.Localization.LocalizedString" + } + ], + "ReturnType": "System.String", + "Static": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "get_Name", + "Parameters": [], + "ReturnType": "System.String", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "get_Value", + "Parameters": [], + "ReturnType": "System.String", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "get_ResourceNotFound", + "Parameters": [], + "ReturnType": "System.Boolean", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "get_SearchedLocation", + "Parameters": [], + "ReturnType": "System.String", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "ToString", + "Parameters": [], + "ReturnType": "System.String", + "Virtual": true, + "Override": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Constructor", + "Name": ".ctor", + "Parameters": [ + { + "Name": "name", + "Type": "System.String" + }, + { + "Name": "value", + "Type": "System.String" + } + ], + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Constructor", + "Name": ".ctor", + "Parameters": [ + { + "Name": "name", + "Type": "System.String" + }, + { + "Name": "value", + "Type": "System.String" + }, + { + "Name": "resourceNotFound", + "Type": "System.Boolean" + } + ], + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Constructor", + "Name": ".ctor", + "Parameters": [ + { + "Name": "name", + "Type": "System.String" + }, + { + "Name": "value", + "Type": "System.String" + }, + { + "Name": "resourceNotFound", + "Type": "System.Boolean" + }, + { + "Name": "searchedLocation", + "Type": "System.String" + } + ], + "Visibility": "Public", + "GenericParameter": [] + } + ], + "GenericParameters": [] + }, + { + "Name": "Microsoft.Extensions.Localization.StringLocalizerExtensions", + "Visibility": "Public", + "Kind": "Class", + "Abstract": true, + "Static": true, + "Sealed": true, + "ImplementedInterfaces": [], + "Members": [ + { + "Kind": "Method", + "Name": "GetString", + "Parameters": [ + { + "Name": "stringLocalizer", + "Type": "Microsoft.Extensions.Localization.IStringLocalizer" + }, + { + "Name": "name", + "Type": "System.String" + } + ], + "ReturnType": "Microsoft.Extensions.Localization.LocalizedString", + "Static": true, + "Extension": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "GetString", + "Parameters": [ + { + "Name": "stringLocalizer", + "Type": "Microsoft.Extensions.Localization.IStringLocalizer" + }, + { + "Name": "name", + "Type": "System.String" + }, + { + "Name": "arguments", + "Type": "System.Object[]", + "IsParams": true + } + ], + "ReturnType": "Microsoft.Extensions.Localization.LocalizedString", + "Static": true, + "Extension": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "GetAllStrings", + "Parameters": [ + { + "Name": "stringLocalizer", + "Type": "Microsoft.Extensions.Localization.IStringLocalizer" + } + ], + "ReturnType": "System.Collections.Generic.IEnumerable", + "Static": true, + "Extension": true, + "Visibility": "Public", + "GenericParameter": [] + } + ], + "GenericParameters": [] + }, + { + "Name": "Microsoft.Extensions.Localization.StringLocalizer", + "Visibility": "Public", + "Kind": "Class", + "ImplementedInterfaces": [ + "Microsoft.Extensions.Localization.IStringLocalizer" + ], + "Members": [ + { + "Kind": "Method", + "Name": "get_Item", + "Parameters": [ + { + "Name": "name", + "Type": "System.String" + } + ], + "ReturnType": "Microsoft.Extensions.Localization.LocalizedString", + "Virtual": true, + "ImplementedInterface": "Microsoft.Extensions.Localization.IStringLocalizer", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "get_Item", + "Parameters": [ + { + "Name": "name", + "Type": "System.String" + }, + { + "Name": "arguments", + "Type": "System.Object[]", + "IsParams": true + } + ], + "ReturnType": "Microsoft.Extensions.Localization.LocalizedString", + "Virtual": true, + "ImplementedInterface": "Microsoft.Extensions.Localization.IStringLocalizer", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "GetAllStrings", + "Parameters": [ + { + "Name": "includeParentCultures", + "Type": "System.Boolean" + } + ], + "ReturnType": "System.Collections.Generic.IEnumerable", + "Sealed": true, + "Virtual": true, + "ImplementedInterface": "Microsoft.Extensions.Localization.IStringLocalizer", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "WithCulture", + "Parameters": [ + { + "Name": "culture", + "Type": "System.Globalization.CultureInfo" + } + ], + "ReturnType": "Microsoft.Extensions.Localization.IStringLocalizer", + "Virtual": true, + "ImplementedInterface": "Microsoft.Extensions.Localization.IStringLocalizer", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Constructor", + "Name": ".ctor", + "Parameters": [ + { + "Name": "factory", + "Type": "Microsoft.Extensions.Localization.IStringLocalizerFactory" + } + ], + "Visibility": "Public", + "GenericParameter": [] + } + ], + "GenericParameters": [ + { + "ParameterName": "TResourceSource", + "ParameterPosition": 0, + "BaseTypeOrInterfaces": [] + } + ] + } + ] +} \ No newline at end of file diff --git a/src/Localization/Localization/src/IResourceNamesCache.cs b/src/Localization/Localization/src/IResourceNamesCache.cs new file mode 100644 index 0000000000..90d104aa68 --- /dev/null +++ b/src/Localization/Localization/src/IResourceNamesCache.cs @@ -0,0 +1,22 @@ +// 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.Collections.Generic; + +namespace Microsoft.Extensions.Localization +{ + /// + /// Represents a cache of string names in resources. + /// + public interface IResourceNamesCache + { + /// + /// Adds a set of resource names to the cache by using the specified function, if the name does not already exist. + /// + /// The resource name to add string names for. + /// The function used to generate the string names for the resource. + /// The string names for the resource. + IList GetOrAdd(string name, Func> valueFactory); + } +} diff --git a/src/Localization/Localization/src/Internal/AssemblyWrapper.cs b/src/Localization/Localization/src/Internal/AssemblyWrapper.cs new file mode 100644 index 0000000000..b0c3c2bce1 --- /dev/null +++ b/src/Localization/Localization/src/Internal/AssemblyWrapper.cs @@ -0,0 +1,28 @@ +// 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.IO; +using System.Reflection; + +namespace Microsoft.Extensions.Localization.Internal +{ + public class AssemblyWrapper + { + public AssemblyWrapper(Assembly assembly) + { + if (assembly == null) + { + throw new ArgumentNullException(nameof(assembly)); + } + + Assembly = assembly; + } + + public Assembly Assembly { get; } + + public virtual string FullName => Assembly.FullName; + + public virtual Stream GetManifestResourceStream(string name) => Assembly.GetManifestResourceStream(name); + } +} diff --git a/src/Localization/Localization/src/Internal/IResourceStringProvider.cs b/src/Localization/Localization/src/Internal/IResourceStringProvider.cs new file mode 100644 index 0000000000..b74bd80eda --- /dev/null +++ b/src/Localization/Localization/src/Internal/IResourceStringProvider.cs @@ -0,0 +1,13 @@ +// 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.Collections.Generic; +using System.Globalization; + +namespace Microsoft.Extensions.Localization.Internal +{ + public interface IResourceStringProvider + { + IList GetAllResourceStrings(CultureInfo culture, bool throwOnMissing); + } +} diff --git a/src/Localization/Localization/src/Internal/ResourceManagerStringLocalizerLoggerExtensions.cs b/src/Localization/Localization/src/Internal/ResourceManagerStringLocalizerLoggerExtensions.cs new file mode 100644 index 0000000000..456e07009e --- /dev/null +++ b/src/Localization/Localization/src/Internal/ResourceManagerStringLocalizerLoggerExtensions.cs @@ -0,0 +1,27 @@ +// 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.Globalization; +using Microsoft.Extensions.Logging; + +namespace Microsoft.Extensions.Localization.Internal +{ + internal static class ResourceManagerStringLocalizerLoggerExtensions + { + private static readonly Action _searchedLocation; + + static ResourceManagerStringLocalizerLoggerExtensions() + { + _searchedLocation = LoggerMessage.Define( + LogLevel.Debug, + 1, + $"{nameof(ResourceManagerStringLocalizer)} searched for '{{Key}}' in '{{LocationSearched}}' with culture '{{Culture}}'."); + } + + public static void SearchedLocation(this ILogger logger, string key, string searchedLocation, CultureInfo culture) + { + _searchedLocation(logger, key, searchedLocation, culture, null); + } + } +} diff --git a/src/Localization/Localization/src/Internal/ResourceManagerStringProvider.cs b/src/Localization/Localization/src/Internal/ResourceManagerStringProvider.cs new file mode 100644 index 0000000000..9eef8c84a8 --- /dev/null +++ b/src/Localization/Localization/src/Internal/ResourceManagerStringProvider.cs @@ -0,0 +1,80 @@ +// 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.Collections; +using System.Collections.Generic; +using System.Globalization; +using System.Reflection; +using System.Resources; + +namespace Microsoft.Extensions.Localization.Internal +{ + public class ResourceManagerStringProvider : IResourceStringProvider + { + private readonly IResourceNamesCache _resourceNamesCache; + private readonly ResourceManager _resourceManager; + private readonly Assembly _assembly; + private readonly string _resourceBaseName; + + public ResourceManagerStringProvider( + IResourceNamesCache resourceCache, + ResourceManager resourceManager, + Assembly assembly, + string baseName) + { + _resourceManager = resourceManager; + _resourceNamesCache = resourceCache; + _assembly = assembly; + _resourceBaseName = baseName; + } + + private string GetResourceCacheKey(CultureInfo culture) + { + var resourceName = _resourceManager.BaseName; + + return $"Culture={culture.Name};resourceName={resourceName};Assembly={_assembly.FullName}"; + } + + private string GetResourceName(CultureInfo culture) + { + var resourceStreamName = _resourceBaseName; + if (!string.IsNullOrEmpty(culture.Name)) + { + resourceStreamName += "." + culture.Name; + } + resourceStreamName += ".resources"; + + return resourceStreamName; + } + + public IList GetAllResourceStrings(CultureInfo culture, bool throwOnMissing) + { + var cacheKey = GetResourceCacheKey(culture); + + return _resourceNamesCache.GetOrAdd(cacheKey, _ => + { + // We purposly don't dispose the ResourceSet because it causes an ObjectDisposedException when you try to read the values later. + var resourceSet = _resourceManager.GetResourceSet(culture, createIfNotExists: true, tryParents: false); + if (resourceSet == null) + { + if (throwOnMissing) + { + throw new MissingManifestResourceException(Resources.FormatLocalization_MissingManifest(GetResourceName(culture))); + } + else + { + return null; + } + } + + var names = new List(); + foreach (DictionaryEntry entry in resourceSet) + { + names.Add((string)entry.Key); + } + + return names; + }); + } + } +} diff --git a/src/Localization/Localization/src/LocalizationOptions.cs b/src/Localization/Localization/src/LocalizationOptions.cs new file mode 100644 index 0000000000..1b7408fe67 --- /dev/null +++ b/src/Localization/Localization/src/LocalizationOptions.cs @@ -0,0 +1,16 @@ +// 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. + +namespace Microsoft.Extensions.Localization +{ + /// + /// Provides programmatic configuration for localization. + /// + public class LocalizationOptions + { + /// + /// The relative path under application root where resource files are located. + /// + public string ResourcesPath { get; set; } = string.Empty; + } +} diff --git a/src/Localization/Localization/src/LocalizationServiceCollectionExtensions.cs b/src/Localization/Localization/src/LocalizationServiceCollectionExtensions.cs new file mode 100644 index 0000000000..111c1c40d9 --- /dev/null +++ b/src/Localization/Localization/src/LocalizationServiceCollectionExtensions.cs @@ -0,0 +1,76 @@ +// 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 Microsoft.Extensions.DependencyInjection.Extensions; +using Microsoft.Extensions.Localization; + +namespace Microsoft.Extensions.DependencyInjection +{ + /// + /// Extension methods for setting up localization services in an . + /// + public static class LocalizationServiceCollectionExtensions + { + /// + /// Adds services required for application localization. + /// + /// The to add the services to. + /// The so that additional calls can be chained. + public static IServiceCollection AddLocalization(this IServiceCollection services) + { + if (services == null) + { + throw new ArgumentNullException(nameof(services)); + } + + services.AddOptions(); + + AddLocalizationServices(services); + + return services; + } + + /// + /// Adds services required for application localization. + /// + /// The to add the services to. + /// + /// An to configure the . + /// + /// The so that additional calls can be chained. + public static IServiceCollection AddLocalization( + this IServiceCollection services, + Action setupAction) + { + if (services == null) + { + throw new ArgumentNullException(nameof(services)); + } + + if (setupAction == null) + { + throw new ArgumentNullException(nameof(setupAction)); + } + + AddLocalizationServices(services, setupAction); + + return services; + } + + // To enable unit testing + internal static void AddLocalizationServices(IServiceCollection services) + { + services.TryAddSingleton(); + services.TryAddTransient(typeof(IStringLocalizer<>), typeof(StringLocalizer<>)); + } + + internal static void AddLocalizationServices( + IServiceCollection services, + Action setupAction) + { + AddLocalizationServices(services); + services.Configure(setupAction); + } + } +} \ No newline at end of file diff --git a/src/Localization/Localization/src/Microsoft.Extensions.Localization.csproj b/src/Localization/Localization/src/Microsoft.Extensions.Localization.csproj new file mode 100644 index 0000000000..73365a15eb --- /dev/null +++ b/src/Localization/Localization/src/Microsoft.Extensions.Localization.csproj @@ -0,0 +1,19 @@ + + + + Microsoft .NET Extensions + Application localization services and default implementation based on ResourceManager to load localized assembly resources. + netstandard2.0 + $(NoWarn);CS1591 + true + localization + + + + + + + + + + diff --git a/src/Localization/Localization/src/Properties/AssemblyInfo.cs b/src/Localization/Localization/src/Properties/AssemblyInfo.cs new file mode 100644 index 0000000000..3e297b801e --- /dev/null +++ b/src/Localization/Localization/src/Properties/AssemblyInfo.cs @@ -0,0 +1,6 @@ +// 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.Runtime.CompilerServices; + +[assembly: InternalsVisibleTo("Microsoft.Extensions.Localization.Tests, PublicKey=0024000004800000940000000602000000240000525341310004000001000100f33a29044fa9d740c9b3213a93e57c84b472c84e0b8a0e1ae48e67a9f8f6de9d5f7f3d52ac23e48ac51801f1dc950abe901da34d2a9e3baadb141a17c77ef3c565dd5ee5054b91cf63bb3c6ab83f72ab3aafe93d0fc3c2348b764fafb0b1c0733de51459aeab46580384bf9d74c4e28164b7cde247f891ba07891c9d872ad2bb")] diff --git a/src/Localization/Localization/src/Properties/Resources.Designer.cs b/src/Localization/Localization/src/Properties/Resources.Designer.cs new file mode 100644 index 0000000000..1123d648ad --- /dev/null +++ b/src/Localization/Localization/src/Properties/Resources.Designer.cs @@ -0,0 +1,62 @@ +// +namespace Microsoft.Extensions.Localization +{ + using System.Globalization; + using System.Reflection; + using System.Resources; + + internal static class Resources + { + private static readonly ResourceManager _resourceManager + = new ResourceManager("Microsoft.Extensions.Localization.Resources", typeof(Resources).GetTypeInfo().Assembly); + + /// + /// The manifest '{0}' was not found. + /// + internal static string Localization_MissingManifest + { + get { return GetString("Localization_MissingManifest"); } + } + + /// + /// The manifest '{0}' was not found. + /// + internal static string FormatLocalization_MissingManifest(object p0) + { + return string.Format(CultureInfo.CurrentCulture, GetString("Localization_MissingManifest"), p0); + } + + /// + /// No manifests exist for the current culture. + /// + internal static string Localization_MissingManifest_Parent + { + get { return GetString("Localization_MissingManifest_Parent"); } + } + + /// + /// No manifests exist for the current culture. + /// + internal static string FormatLocalization_MissingManifest_Parent() + { + return GetString("Localization_MissingManifest_Parent"); + } + + private static string GetString(string name, params string[] formatterNames) + { + var value = _resourceManager.GetString(name); + + System.Diagnostics.Debug.Assert(value != null); + + if (formatterNames != null) + { + for (var i = 0; i < formatterNames.Length; i++) + { + value = value.Replace("{" + formatterNames[i] + "}", "{" + i + "}"); + } + } + + return value; + } + } +} diff --git a/src/Localization/Localization/src/ResourceLocationAttribute.cs b/src/Localization/Localization/src/ResourceLocationAttribute.cs new file mode 100644 index 0000000000..5bf281d90e --- /dev/null +++ b/src/Localization/Localization/src/ResourceLocationAttribute.cs @@ -0,0 +1,33 @@ +// 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; + +namespace Microsoft.Extensions.Localization +{ + /// + /// Provides the location of resources for an Assembly. + /// + [AttributeUsage(AttributeTargets.Assembly, AllowMultiple = false, Inherited = false)] + public class ResourceLocationAttribute : Attribute + { + /// + /// Creates a new . + /// + /// The location of resources for this Assembly. + public ResourceLocationAttribute(string resourceLocation) + { + if (string.IsNullOrEmpty(resourceLocation)) + { + throw new ArgumentNullException(nameof(resourceLocation)); + } + + ResourceLocation = resourceLocation; + } + + /// + /// The location of resources for this Assembly. + /// + public string ResourceLocation { get; } + } +} diff --git a/src/Localization/Localization/src/ResourceManagerStringLocalizer.cs b/src/Localization/Localization/src/ResourceManagerStringLocalizer.cs new file mode 100644 index 0000000000..e2e1a3f234 --- /dev/null +++ b/src/Localization/Localization/src/ResourceManagerStringLocalizer.cs @@ -0,0 +1,274 @@ +// 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.Collections.Concurrent; +using System.Collections.Generic; +using System.Globalization; +using System.Reflection; +using System.Resources; +using Microsoft.Extensions.Localization.Internal; +using Microsoft.Extensions.Logging; + +namespace Microsoft.Extensions.Localization +{ + /// + /// An that uses the and + /// to provide localized strings. + /// + /// This type is thread-safe. + public class ResourceManagerStringLocalizer : IStringLocalizer + { + private readonly ConcurrentDictionary _missingManifestCache = new ConcurrentDictionary(); + private readonly IResourceNamesCache _resourceNamesCache; + private readonly ResourceManager _resourceManager; + private readonly IResourceStringProvider _resourceStringProvider; + private readonly string _resourceBaseName; + private readonly ILogger _logger; + + /// + /// Creates a new . + /// + /// The to read strings from. + /// The that contains the strings as embedded resources. + /// The base name of the embedded resource that contains the strings. + /// Cache of the list of strings for a given resource assembly name. + /// The . + public ResourceManagerStringLocalizer( + ResourceManager resourceManager, + Assembly resourceAssembly, + string baseName, + IResourceNamesCache resourceNamesCache, + ILogger logger) + : this( + resourceManager, + new AssemblyWrapper(resourceAssembly), + baseName, + resourceNamesCache, + logger) + { + } + + /// + /// Intended for testing purposes only. + /// + public ResourceManagerStringLocalizer( + ResourceManager resourceManager, + AssemblyWrapper resourceAssemblyWrapper, + string baseName, + IResourceNamesCache resourceNamesCache, + ILogger logger) + : this( + resourceManager, + new ResourceManagerStringProvider( + resourceNamesCache, + resourceManager, + resourceAssemblyWrapper.Assembly, + baseName), + baseName, + resourceNamesCache, + logger) + { + } + + /// + /// Intended for testing purposes only. + /// + public ResourceManagerStringLocalizer( + ResourceManager resourceManager, + IResourceStringProvider resourceStringProvider, + string baseName, + IResourceNamesCache resourceNamesCache, + ILogger logger) + { + if (resourceManager == null) + { + throw new ArgumentNullException(nameof(resourceManager)); + } + + if (resourceStringProvider == null) + { + throw new ArgumentNullException(nameof(resourceStringProvider)); + } + + if (baseName == null) + { + throw new ArgumentNullException(nameof(baseName)); + } + + if (resourceNamesCache == null) + { + throw new ArgumentNullException(nameof(resourceNamesCache)); + } + + if (logger == null) + { + throw new ArgumentNullException(nameof(logger)); + } + + _resourceStringProvider = resourceStringProvider; + _resourceManager = resourceManager; + _resourceBaseName = baseName; + _resourceNamesCache = resourceNamesCache; + _logger = logger; + } + + /// + public virtual LocalizedString this[string name] + { + get + { + if (name == null) + { + throw new ArgumentNullException(nameof(name)); + } + + var value = GetStringSafely(name, null); + + return new LocalizedString(name, value ?? name, resourceNotFound: value == null, searchedLocation: _resourceBaseName); + } + } + + /// + public virtual LocalizedString this[string name, params object[] arguments] + { + get + { + if (name == null) + { + throw new ArgumentNullException(nameof(name)); + } + + var format = GetStringSafely(name, null); + var value = string.Format(format ?? name, arguments); + + return new LocalizedString(name, value, resourceNotFound: format == null, searchedLocation: _resourceBaseName); + } + } + + /// + /// Creates a new for a specific . + /// + /// The to use. + /// A culture-specific . + public IStringLocalizer WithCulture(CultureInfo culture) + { + return culture == null + ? new ResourceManagerStringLocalizer( + _resourceManager, + _resourceStringProvider, + _resourceBaseName, + _resourceNamesCache, + _logger) + : new ResourceManagerWithCultureStringLocalizer( + _resourceManager, + _resourceStringProvider, + _resourceBaseName, + _resourceNamesCache, + culture, + _logger); + } + + /// + public virtual IEnumerable GetAllStrings(bool includeParentCultures) => + GetAllStrings(includeParentCultures, CultureInfo.CurrentUICulture); + + /// + /// Returns all strings in the specified culture. + /// + /// + /// The to get strings for. + /// The strings. + protected IEnumerable GetAllStrings(bool includeParentCultures, CultureInfo culture) + { + if (culture == null) + { + throw new ArgumentNullException(nameof(culture)); + } + + var resourceNames = includeParentCultures + ? GetResourceNamesFromCultureHierarchy(culture) + : _resourceStringProvider.GetAllResourceStrings(culture, true); + + foreach (var name in resourceNames) + { + var value = GetStringSafely(name, culture); + yield return new LocalizedString(name, value ?? name, resourceNotFound: value == null, searchedLocation: _resourceBaseName); + } + } + + /// + /// Gets a resource string from the and returns null instead of + /// throwing exceptions if a match isn't found. + /// + /// The name of the string resource. + /// The to get the string for. + /// The resource string, or null if none was found. + protected string GetStringSafely(string name, CultureInfo culture) + { + if (name == null) + { + throw new ArgumentNullException(nameof(name)); + } + + var keyCulture = culture ?? CultureInfo.CurrentUICulture; + + var cacheKey = $"name={name}&culture={keyCulture.Name}"; + + _logger.SearchedLocation(name, _resourceBaseName, keyCulture); + + if (_missingManifestCache.ContainsKey(cacheKey)) + { + return null; + } + + try + { + return culture == null ? _resourceManager.GetString(name) : _resourceManager.GetString(name, culture); + } + catch (MissingManifestResourceException) + { + _missingManifestCache.TryAdd(cacheKey, null); + return null; + } + } + + private IEnumerable GetResourceNamesFromCultureHierarchy(CultureInfo startingCulture) + { + var currentCulture = startingCulture; + var resourceNames = new HashSet(); + + var hasAnyCultures = false; + + while (true) + { + + var cultureResourceNames = _resourceStringProvider.GetAllResourceStrings(currentCulture, false); + + if (cultureResourceNames != null) + { + foreach (var resourceName in cultureResourceNames) + { + resourceNames.Add(resourceName); + } + hasAnyCultures = true; + } + + if (currentCulture == currentCulture.Parent) + { + // currentCulture begat currentCulture, probably time to leave + break; + } + + currentCulture = currentCulture.Parent; + } + + if (!hasAnyCultures) + { + throw new MissingManifestResourceException(Resources.Localization_MissingManifest_Parent); + } + + return resourceNames; + } + } +} \ No newline at end of file diff --git a/src/Localization/Localization/src/ResourceManagerStringLocalizerFactory.cs b/src/Localization/Localization/src/ResourceManagerStringLocalizerFactory.cs new file mode 100644 index 0000000000..2eb737eaa7 --- /dev/null +++ b/src/Localization/Localization/src/ResourceManagerStringLocalizerFactory.cs @@ -0,0 +1,270 @@ +// 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.Collections.Concurrent; +using System.IO; +using System.Reflection; +using System.Resources; +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Options; + +namespace Microsoft.Extensions.Localization +{ + /// + /// An that creates instances of . + /// + /// + /// offers multiple ways to set the relative path of + /// resources to be used. They are, in order of precedence: + /// -> -> the project root. + /// + public class ResourceManagerStringLocalizerFactory : IStringLocalizerFactory + { + private readonly IResourceNamesCache _resourceNamesCache = new ResourceNamesCache(); + private readonly ConcurrentDictionary _localizerCache = + new ConcurrentDictionary(); + private readonly string _resourcesRelativePath; + private readonly ILoggerFactory _loggerFactory; + + /// + /// Creates a new . + /// + /// The . + /// The . + public ResourceManagerStringLocalizerFactory( + IOptions localizationOptions, + ILoggerFactory loggerFactory) + { + if (localizationOptions == null) + { + throw new ArgumentNullException(nameof(localizationOptions)); + } + + if (loggerFactory == null) + { + throw new ArgumentNullException(nameof(loggerFactory)); + } + + _resourcesRelativePath = localizationOptions.Value.ResourcesPath ?? string.Empty; + _loggerFactory = loggerFactory; + + if (!string.IsNullOrEmpty(_resourcesRelativePath)) + { + _resourcesRelativePath = _resourcesRelativePath.Replace(Path.AltDirectorySeparatorChar, '.') + .Replace(Path.DirectorySeparatorChar, '.') + "."; + } + } + + /// + /// Gets the resource prefix used to look up the resource. + /// + /// The type of the resource to be looked up. + /// The prefix for resource lookup. + protected virtual string GetResourcePrefix(TypeInfo typeInfo) + { + if (typeInfo == null) + { + throw new ArgumentNullException(nameof(typeInfo)); + } + + return GetResourcePrefix(typeInfo, GetRootNamespace(typeInfo.Assembly), GetResourcePath(typeInfo.Assembly)); + } + + /// + /// Gets the resource prefix used to look up the resource. + /// + /// The type of the resource to be looked up. + /// The base namespace of the application. + /// The folder containing all resources. + /// The prefix for resource lookup. + /// + /// For the type "Sample.Controllers.Home" if there's a resourceRelativePath return + /// "Sample.Resourcepath.Controllers.Home" if there isn't one then it would return "Sample.Controllers.Home". + /// + protected virtual string GetResourcePrefix(TypeInfo typeInfo, string baseNamespace, string resourcesRelativePath) + { + if (typeInfo == null) + { + throw new ArgumentNullException(nameof(typeInfo)); + } + + if (string.IsNullOrEmpty(baseNamespace)) + { + throw new ArgumentNullException(nameof(baseNamespace)); + } + + if (string.IsNullOrEmpty(resourcesRelativePath)) + { + return typeInfo.FullName; + } + else + { + // This expectation is defined by dotnet's automatic resource storage. + // We have to conform to "{RootNamespace}.{ResourceLocation}.{FullTypeName - AssemblyName}". + var assemblyName = new AssemblyName(typeInfo.Assembly.FullName).Name; + return baseNamespace + "." + resourcesRelativePath + TrimPrefix(typeInfo.FullName, assemblyName + "."); + } + } + + /// + /// Gets the resource prefix used to look up the resource. + /// + /// The name of the resource to be looked up + /// The base namespace of the application. + /// The prefix for resource lookup. + protected virtual string GetResourcePrefix(string baseResourceName, string baseNamespace) + { + if (string.IsNullOrEmpty(baseResourceName)) + { + throw new ArgumentNullException(nameof(baseResourceName)); + } + + if (string.IsNullOrEmpty(baseNamespace)) + { + throw new ArgumentNullException(nameof(baseNamespace)); + } + + var assemblyName = new AssemblyName(baseNamespace); + var assembly = Assembly.Load(assemblyName); + var rootNamespace = GetRootNamespace(assembly); + var resourceLocation = GetResourcePath(assembly); + var locationPath = rootNamespace + "." + resourceLocation; + + baseResourceName = locationPath + TrimPrefix(baseResourceName, baseNamespace + "."); + + return baseResourceName; + } + + /// + /// Creates a using the and + /// of the specified . + /// + /// The . + /// The . + public IStringLocalizer Create(Type resourceSource) + { + if (resourceSource == null) + { + throw new ArgumentNullException(nameof(resourceSource)); + } + + var typeInfo = resourceSource.GetTypeInfo(); + + var baseName = GetResourcePrefix(typeInfo); + + var assembly = typeInfo.Assembly; + + return _localizerCache.GetOrAdd(baseName, _ => CreateResourceManagerStringLocalizer(assembly, baseName)); + } + + /// + /// Creates a . + /// + /// The base name of the resource to load strings from. + /// The location to load resources from. + /// The . + public IStringLocalizer Create(string baseName, string location) + { + if (baseName == null) + { + throw new ArgumentNullException(nameof(baseName)); + } + + if (location == null) + { + throw new ArgumentNullException(nameof(location)); + } + + return _localizerCache.GetOrAdd($"B={baseName},L={location}", _ => + { + var assemblyName = new AssemblyName(location); + var assembly = Assembly.Load(assemblyName); + baseName = GetResourcePrefix(baseName, location); + + return CreateResourceManagerStringLocalizer(assembly, baseName); + }); + } + + /// Creates a for the given input. + /// The assembly to create a for. + /// The base name of the resource to search for. + /// A for the given and . + /// This method is virtual for testing purposes only. + protected virtual ResourceManagerStringLocalizer CreateResourceManagerStringLocalizer( + Assembly assembly, + string baseName) + { + return new ResourceManagerStringLocalizer( + new ResourceManager(baseName, assembly), + assembly, + baseName, + _resourceNamesCache, + _loggerFactory.CreateLogger()); + } + + /// + /// Gets the resource prefix used to look up the resource. + /// + /// The general location of the resource. + /// The base name of the resource. + /// The location of the resource within . + /// The resource prefix used to look up the resource. + protected virtual string GetResourcePrefix(string location, string baseName, string resourceLocation) + { + // Re-root the base name if a resources path is set + return location + "." + resourceLocation + TrimPrefix(baseName, location + "."); + } + + /// Gets a from the provided . + /// The assembly to get a from. + /// The associated with the given . + /// This method is protected and virtual for testing purposes only. + protected virtual ResourceLocationAttribute GetResourceLocationAttribute(Assembly assembly) + { + return assembly.GetCustomAttribute(); + } + + /// Gets a from the provided . + /// The assembly to get a from. + /// The associated with the given . + /// This method is protected and virtual for testing purposes only. + protected virtual RootNamespaceAttribute GetRootNamespaceAttribute(Assembly assembly) + { + return assembly.GetCustomAttribute(); + } + + private string GetRootNamespace(Assembly assembly) + { + var rootNamespaceAttribute = GetRootNamespaceAttribute(assembly); + + return rootNamespaceAttribute?.RootNamespace ?? + new AssemblyName(assembly.FullName).Name; + } + + private string GetResourcePath(Assembly assembly) + { + var resourceLocationAttribute = GetResourceLocationAttribute(assembly); + + // If we don't have an attribute assume all assemblies use the same resource location. + var resourceLocation = resourceLocationAttribute == null + ? _resourcesRelativePath + : resourceLocationAttribute.ResourceLocation + "."; + resourceLocation = resourceLocation + .Replace(Path.DirectorySeparatorChar, '.') + .Replace(Path.AltDirectorySeparatorChar, '.'); + + return resourceLocation; + } + + private static string TrimPrefix(string name, string prefix) + { + if (name.StartsWith(prefix, StringComparison.Ordinal)) + { + return name.Substring(prefix.Length); + } + + return name; + } + } +} \ No newline at end of file diff --git a/src/Localization/Localization/src/ResourceManagerWithCultureStringLocalizer.cs b/src/Localization/Localization/src/ResourceManagerWithCultureStringLocalizer.cs new file mode 100644 index 0000000000..65b6ae242c --- /dev/null +++ b/src/Localization/Localization/src/ResourceManagerWithCultureStringLocalizer.cs @@ -0,0 +1,164 @@ +// 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.Collections.Generic; +using System.Globalization; +using System.Reflection; +using System.Resources; +using Microsoft.Extensions.Localization.Internal; +using Microsoft.Extensions.Logging; + +namespace Microsoft.Extensions.Localization +{ + /// + /// An that uses the and + /// to provide localized strings for a specific . + /// + public class ResourceManagerWithCultureStringLocalizer : ResourceManagerStringLocalizer + { + private readonly string _resourceBaseName; + private readonly CultureInfo _culture; + + /// + /// Creates a new . + /// + /// The to read strings from. + /// The that can find the resources. + /// The base name of the embedded resource that contains the strings. + /// Cache of the list of strings for a given resource assembly name. + /// The specific to use. + /// The . + internal ResourceManagerWithCultureStringLocalizer( + ResourceManager resourceManager, + IResourceStringProvider resourceStringProvider, + string baseName, + IResourceNamesCache resourceNamesCache, + CultureInfo culture, + ILogger logger) + : base(resourceManager, resourceStringProvider, baseName, resourceNamesCache, logger) + { + if (resourceManager == null) + { + throw new ArgumentNullException(nameof(resourceManager)); + } + + if (resourceStringProvider == null) + { + throw new ArgumentNullException(nameof(resourceStringProvider)); + } + + if (baseName == null) + { + throw new ArgumentNullException(nameof(baseName)); + } + + if (resourceNamesCache == null) + { + throw new ArgumentNullException(nameof(resourceNamesCache)); + } + + if (culture == null) + { + throw new ArgumentNullException(nameof(culture)); + } + + if (logger == null) + { + throw new ArgumentNullException(nameof(logger)); + } + + _resourceBaseName = baseName; + _culture = culture; + } + + /// + /// Creates a new . + /// + /// The to read strings from. + /// The that contains the strings as embedded resources. + /// The base name of the embedded resource that contains the strings. + /// Cache of the list of strings for a given resource assembly name. + /// The specific to use. + /// The . + public ResourceManagerWithCultureStringLocalizer( + ResourceManager resourceManager, + Assembly resourceAssembly, + string baseName, + IResourceNamesCache resourceNamesCache, + CultureInfo culture, + ILogger logger) + : base(resourceManager, resourceAssembly, baseName, resourceNamesCache, logger) + { + if (resourceManager == null) + { + throw new ArgumentNullException(nameof(resourceManager)); + } + + if (resourceAssembly == null) + { + throw new ArgumentNullException(nameof(resourceAssembly)); + } + + if (baseName == null) + { + throw new ArgumentNullException(nameof(baseName)); + } + + if (resourceNamesCache == null) + { + throw new ArgumentNullException(nameof(resourceNamesCache)); + } + + if (culture == null) + { + throw new ArgumentNullException(nameof(culture)); + } + + if (logger == null) + { + throw new ArgumentNullException(nameof(logger)); + } + + _resourceBaseName = baseName; + _culture = culture; + } + + /// + public override LocalizedString this[string name] + { + get + { + if (name == null) + { + throw new ArgumentNullException(nameof(name)); + } + + var value = GetStringSafely(name, _culture); + + return new LocalizedString(name, value ?? name, resourceNotFound: value == null, searchedLocation: _resourceBaseName); + } + } + + /// + public override LocalizedString this[string name, params object[] arguments] + { + get + { + if (name == null) + { + throw new ArgumentNullException(nameof(name)); + } + + var format = GetStringSafely(name, _culture); + var value = string.Format(_culture, format ?? name, arguments); + + return new LocalizedString(name, value, resourceNotFound: format == null, searchedLocation: _resourceBaseName); + } + } + + /// + public override IEnumerable GetAllStrings(bool includeParentCultures) => + GetAllStrings(includeParentCultures, _culture); + } +} \ No newline at end of file diff --git a/src/Localization/Localization/src/ResourceNamesCache.cs b/src/Localization/Localization/src/ResourceNamesCache.cs new file mode 100644 index 0000000000..86e94c102a --- /dev/null +++ b/src/Localization/Localization/src/ResourceNamesCache.cs @@ -0,0 +1,23 @@ +// 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.Collections.Concurrent; +using System.Collections.Generic; + +namespace Microsoft.Extensions.Localization +{ + /// + /// An implementation of backed by a . + /// + public class ResourceNamesCache : IResourceNamesCache + { + private readonly ConcurrentDictionary> _cache = new ConcurrentDictionary>(); + + /// + public IList GetOrAdd(string name, Func> valueFactory) + { + return _cache.GetOrAdd(name, valueFactory); + } + } +} diff --git a/src/Localization/Localization/src/Resources.resx b/src/Localization/Localization/src/Resources.resx new file mode 100644 index 0000000000..b679f04664 --- /dev/null +++ b/src/Localization/Localization/src/Resources.resx @@ -0,0 +1,126 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + The manifest '{0}' was not found. + + + No manifests exist for the current culture. + + \ No newline at end of file diff --git a/src/Localization/Localization/src/RootNamespaceAttribute.cs b/src/Localization/Localization/src/RootNamespaceAttribute.cs new file mode 100644 index 0000000000..f28b4ea1fd --- /dev/null +++ b/src/Localization/Localization/src/RootNamespaceAttribute.cs @@ -0,0 +1,35 @@ +// 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; + +namespace Microsoft.Extensions.Localization +{ + /// + /// Provides the RootNamespace of an Assembly. The RootNamespace of the assembly is used by Localization to + /// determine the resource name to look for when RootNamespace differs from the AssemblyName. + /// + [AttributeUsage(AttributeTargets.Assembly, AllowMultiple = false, Inherited = false)] + public class RootNamespaceAttribute : Attribute + { + /// + /// Creates a new . + /// + /// The RootNamespace for this Assembly. + public RootNamespaceAttribute(string rootNamespace) + { + if (string.IsNullOrEmpty(rootNamespace)) + { + throw new ArgumentNullException(nameof(rootNamespace)); + } + + RootNamespace = rootNamespace; + } + + /// + /// The RootNamespace of this Assembly. The RootNamespace of the assembly is used by Localization to + /// determine the resource name to look for when RootNamespace differs from the AssemblyName. + /// + public string RootNamespace { get; } + } +} diff --git a/src/Localization/Localization/src/baseline.netcore.json b/src/Localization/Localization/src/baseline.netcore.json new file mode 100644 index 0000000000..860db76899 --- /dev/null +++ b/src/Localization/Localization/src/baseline.netcore.json @@ -0,0 +1,687 @@ +{ + "AssemblyIdentity": "Microsoft.Extensions.Localization, Version=2.1.1.0, Culture=neutral, PublicKeyToken=adb9793829ddae60", + "Types": [ + { + "Name": "Microsoft.Extensions.DependencyInjection.LocalizationServiceCollectionExtensions", + "Visibility": "Public", + "Kind": "Class", + "Abstract": true, + "Static": true, + "Sealed": true, + "ImplementedInterfaces": [], + "Members": [ + { + "Kind": "Method", + "Name": "AddLocalization", + "Parameters": [ + { + "Name": "services", + "Type": "Microsoft.Extensions.DependencyInjection.IServiceCollection" + } + ], + "ReturnType": "Microsoft.Extensions.DependencyInjection.IServiceCollection", + "Static": true, + "Extension": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "AddLocalization", + "Parameters": [ + { + "Name": "services", + "Type": "Microsoft.Extensions.DependencyInjection.IServiceCollection" + }, + { + "Name": "setupAction", + "Type": "System.Action" + } + ], + "ReturnType": "Microsoft.Extensions.DependencyInjection.IServiceCollection", + "Static": true, + "Extension": true, + "Visibility": "Public", + "GenericParameter": [] + } + ], + "GenericParameters": [] + }, + { + "Name": "Microsoft.Extensions.Localization.IResourceNamesCache", + "Visibility": "Public", + "Kind": "Interface", + "Abstract": true, + "ImplementedInterfaces": [], + "Members": [ + { + "Kind": "Method", + "Name": "GetOrAdd", + "Parameters": [ + { + "Name": "name", + "Type": "System.String" + }, + { + "Name": "valueFactory", + "Type": "System.Func>" + } + ], + "ReturnType": "System.Collections.Generic.IList", + "GenericParameter": [] + } + ], + "GenericParameters": [] + }, + { + "Name": "Microsoft.Extensions.Localization.LocalizationOptions", + "Visibility": "Public", + "Kind": "Class", + "ImplementedInterfaces": [], + "Members": [ + { + "Kind": "Method", + "Name": "get_ResourcesPath", + "Parameters": [], + "ReturnType": "System.String", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "set_ResourcesPath", + "Parameters": [ + { + "Name": "value", + "Type": "System.String" + } + ], + "ReturnType": "System.Void", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Constructor", + "Name": ".ctor", + "Parameters": [], + "Visibility": "Public", + "GenericParameter": [] + } + ], + "GenericParameters": [] + }, + { + "Name": "Microsoft.Extensions.Localization.ResourceLocationAttribute", + "Visibility": "Public", + "Kind": "Class", + "BaseType": "System.Attribute", + "ImplementedInterfaces": [], + "Members": [ + { + "Kind": "Method", + "Name": "get_ResourceLocation", + "Parameters": [], + "ReturnType": "System.String", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Constructor", + "Name": ".ctor", + "Parameters": [ + { + "Name": "resourceLocation", + "Type": "System.String" + } + ], + "Visibility": "Public", + "GenericParameter": [] + } + ], + "GenericParameters": [] + }, + { + "Name": "Microsoft.Extensions.Localization.ResourceManagerStringLocalizer", + "Visibility": "Public", + "Kind": "Class", + "ImplementedInterfaces": [ + "Microsoft.Extensions.Localization.IStringLocalizer" + ], + "Members": [ + { + "Kind": "Method", + "Name": "get_Item", + "Parameters": [ + { + "Name": "name", + "Type": "System.String" + } + ], + "ReturnType": "Microsoft.Extensions.Localization.LocalizedString", + "Virtual": true, + "ImplementedInterface": "Microsoft.Extensions.Localization.IStringLocalizer", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "get_Item", + "Parameters": [ + { + "Name": "name", + "Type": "System.String" + }, + { + "Name": "arguments", + "Type": "System.Object[]", + "IsParams": true + } + ], + "ReturnType": "Microsoft.Extensions.Localization.LocalizedString", + "Virtual": true, + "ImplementedInterface": "Microsoft.Extensions.Localization.IStringLocalizer", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "WithCulture", + "Parameters": [ + { + "Name": "culture", + "Type": "System.Globalization.CultureInfo" + } + ], + "ReturnType": "Microsoft.Extensions.Localization.IStringLocalizer", + "Sealed": true, + "Virtual": true, + "ImplementedInterface": "Microsoft.Extensions.Localization.IStringLocalizer", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "GetAllStrings", + "Parameters": [ + { + "Name": "includeParentCultures", + "Type": "System.Boolean" + } + ], + "ReturnType": "System.Collections.Generic.IEnumerable", + "Virtual": true, + "ImplementedInterface": "Microsoft.Extensions.Localization.IStringLocalizer", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "GetAllStrings", + "Parameters": [ + { + "Name": "includeParentCultures", + "Type": "System.Boolean" + }, + { + "Name": "culture", + "Type": "System.Globalization.CultureInfo" + } + ], + "ReturnType": "System.Collections.Generic.IEnumerable", + "Visibility": "Protected", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "GetStringSafely", + "Parameters": [ + { + "Name": "name", + "Type": "System.String" + }, + { + "Name": "culture", + "Type": "System.Globalization.CultureInfo" + } + ], + "ReturnType": "System.String", + "Visibility": "Protected", + "GenericParameter": [] + }, + { + "Kind": "Constructor", + "Name": ".ctor", + "Parameters": [ + { + "Name": "resourceManager", + "Type": "System.Resources.ResourceManager" + }, + { + "Name": "resourceAssembly", + "Type": "System.Reflection.Assembly" + }, + { + "Name": "baseName", + "Type": "System.String" + }, + { + "Name": "resourceNamesCache", + "Type": "Microsoft.Extensions.Localization.IResourceNamesCache" + }, + { + "Name": "logger", + "Type": "Microsoft.Extensions.Logging.ILogger" + } + ], + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Constructor", + "Name": ".ctor", + "Parameters": [ + { + "Name": "resourceManager", + "Type": "System.Resources.ResourceManager" + }, + { + "Name": "resourceAssemblyWrapper", + "Type": "Microsoft.Extensions.Localization.Internal.AssemblyWrapper" + }, + { + "Name": "baseName", + "Type": "System.String" + }, + { + "Name": "resourceNamesCache", + "Type": "Microsoft.Extensions.Localization.IResourceNamesCache" + }, + { + "Name": "logger", + "Type": "Microsoft.Extensions.Logging.ILogger" + } + ], + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Constructor", + "Name": ".ctor", + "Parameters": [ + { + "Name": "resourceManager", + "Type": "System.Resources.ResourceManager" + }, + { + "Name": "resourceStringProvider", + "Type": "Microsoft.Extensions.Localization.Internal.IResourceStringProvider" + }, + { + "Name": "baseName", + "Type": "System.String" + }, + { + "Name": "resourceNamesCache", + "Type": "Microsoft.Extensions.Localization.IResourceNamesCache" + }, + { + "Name": "logger", + "Type": "Microsoft.Extensions.Logging.ILogger" + } + ], + "Visibility": "Public", + "GenericParameter": [] + } + ], + "GenericParameters": [] + }, + { + "Name": "Microsoft.Extensions.Localization.ResourceManagerStringLocalizerFactory", + "Visibility": "Public", + "Kind": "Class", + "ImplementedInterfaces": [ + "Microsoft.Extensions.Localization.IStringLocalizerFactory" + ], + "Members": [ + { + "Kind": "Method", + "Name": "GetResourcePrefix", + "Parameters": [ + { + "Name": "typeInfo", + "Type": "System.Reflection.TypeInfo" + } + ], + "ReturnType": "System.String", + "Virtual": true, + "Visibility": "Protected", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "GetResourcePrefix", + "Parameters": [ + { + "Name": "typeInfo", + "Type": "System.Reflection.TypeInfo" + }, + { + "Name": "baseNamespace", + "Type": "System.String" + }, + { + "Name": "resourcesRelativePath", + "Type": "System.String" + } + ], + "ReturnType": "System.String", + "Virtual": true, + "Visibility": "Protected", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "GetResourcePrefix", + "Parameters": [ + { + "Name": "baseResourceName", + "Type": "System.String" + }, + { + "Name": "baseNamespace", + "Type": "System.String" + } + ], + "ReturnType": "System.String", + "Virtual": true, + "Visibility": "Protected", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "Create", + "Parameters": [ + { + "Name": "resourceSource", + "Type": "System.Type" + } + ], + "ReturnType": "Microsoft.Extensions.Localization.IStringLocalizer", + "Sealed": true, + "Virtual": true, + "ImplementedInterface": "Microsoft.Extensions.Localization.IStringLocalizerFactory", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "Create", + "Parameters": [ + { + "Name": "baseName", + "Type": "System.String" + }, + { + "Name": "location", + "Type": "System.String" + } + ], + "ReturnType": "Microsoft.Extensions.Localization.IStringLocalizer", + "Sealed": true, + "Virtual": true, + "ImplementedInterface": "Microsoft.Extensions.Localization.IStringLocalizerFactory", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "CreateResourceManagerStringLocalizer", + "Parameters": [ + { + "Name": "assembly", + "Type": "System.Reflection.Assembly" + }, + { + "Name": "baseName", + "Type": "System.String" + } + ], + "ReturnType": "Microsoft.Extensions.Localization.ResourceManagerStringLocalizer", + "Virtual": true, + "Visibility": "Protected", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "GetResourcePrefix", + "Parameters": [ + { + "Name": "location", + "Type": "System.String" + }, + { + "Name": "baseName", + "Type": "System.String" + }, + { + "Name": "resourceLocation", + "Type": "System.String" + } + ], + "ReturnType": "System.String", + "Virtual": true, + "Visibility": "Protected", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "GetResourceLocationAttribute", + "Parameters": [ + { + "Name": "assembly", + "Type": "System.Reflection.Assembly" + } + ], + "ReturnType": "Microsoft.Extensions.Localization.ResourceLocationAttribute", + "Virtual": true, + "Visibility": "Protected", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "GetRootNamespaceAttribute", + "Parameters": [ + { + "Name": "assembly", + "Type": "System.Reflection.Assembly" + } + ], + "ReturnType": "Microsoft.Extensions.Localization.RootNamespaceAttribute", + "Virtual": true, + "Visibility": "Protected", + "GenericParameter": [] + }, + { + "Kind": "Constructor", + "Name": ".ctor", + "Parameters": [ + { + "Name": "localizationOptions", + "Type": "Microsoft.Extensions.Options.IOptions" + }, + { + "Name": "loggerFactory", + "Type": "Microsoft.Extensions.Logging.ILoggerFactory" + } + ], + "Visibility": "Public", + "GenericParameter": [] + } + ], + "GenericParameters": [] + }, + { + "Name": "Microsoft.Extensions.Localization.ResourceManagerWithCultureStringLocalizer", + "Visibility": "Public", + "Kind": "Class", + "BaseType": "Microsoft.Extensions.Localization.ResourceManagerStringLocalizer", + "ImplementedInterfaces": [], + "Members": [ + { + "Kind": "Method", + "Name": "get_Item", + "Parameters": [ + { + "Name": "name", + "Type": "System.String" + } + ], + "ReturnType": "Microsoft.Extensions.Localization.LocalizedString", + "Virtual": true, + "Override": true, + "ImplementedInterface": "Microsoft.Extensions.Localization.IStringLocalizer", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "get_Item", + "Parameters": [ + { + "Name": "name", + "Type": "System.String" + }, + { + "Name": "arguments", + "Type": "System.Object[]", + "IsParams": true + } + ], + "ReturnType": "Microsoft.Extensions.Localization.LocalizedString", + "Virtual": true, + "Override": true, + "ImplementedInterface": "Microsoft.Extensions.Localization.IStringLocalizer", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "GetAllStrings", + "Parameters": [ + { + "Name": "includeParentCultures", + "Type": "System.Boolean" + } + ], + "ReturnType": "System.Collections.Generic.IEnumerable", + "Virtual": true, + "Override": true, + "ImplementedInterface": "Microsoft.Extensions.Localization.IStringLocalizer", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Constructor", + "Name": ".ctor", + "Parameters": [ + { + "Name": "resourceManager", + "Type": "System.Resources.ResourceManager" + }, + { + "Name": "resourceAssembly", + "Type": "System.Reflection.Assembly" + }, + { + "Name": "baseName", + "Type": "System.String" + }, + { + "Name": "resourceNamesCache", + "Type": "Microsoft.Extensions.Localization.IResourceNamesCache" + }, + { + "Name": "culture", + "Type": "System.Globalization.CultureInfo" + }, + { + "Name": "logger", + "Type": "Microsoft.Extensions.Logging.ILogger" + } + ], + "Visibility": "Public", + "GenericParameter": [] + } + ], + "GenericParameters": [] + }, + { + "Name": "Microsoft.Extensions.Localization.ResourceNamesCache", + "Visibility": "Public", + "Kind": "Class", + "ImplementedInterfaces": [ + "Microsoft.Extensions.Localization.IResourceNamesCache" + ], + "Members": [ + { + "Kind": "Method", + "Name": "GetOrAdd", + "Parameters": [ + { + "Name": "name", + "Type": "System.String" + }, + { + "Name": "valueFactory", + "Type": "System.Func>" + } + ], + "ReturnType": "System.Collections.Generic.IList", + "Sealed": true, + "Virtual": true, + "ImplementedInterface": "Microsoft.Extensions.Localization.IResourceNamesCache", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Constructor", + "Name": ".ctor", + "Parameters": [], + "Visibility": "Public", + "GenericParameter": [] + } + ], + "GenericParameters": [] + }, + { + "Name": "Microsoft.Extensions.Localization.RootNamespaceAttribute", + "Visibility": "Public", + "Kind": "Class", + "BaseType": "System.Attribute", + "ImplementedInterfaces": [], + "Members": [ + { + "Kind": "Method", + "Name": "get_RootNamespace", + "Parameters": [], + "ReturnType": "System.String", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Constructor", + "Name": ".ctor", + "Parameters": [ + { + "Name": "rootNamespace", + "Type": "System.String" + } + ], + "Visibility": "Public", + "GenericParameter": [] + } + ], + "GenericParameters": [] + } + ] +} \ No newline at end of file diff --git a/src/Localization/Localization/test/LocalizationServiceCollectionExtensionsTest.cs b/src/Localization/Localization/test/LocalizationServiceCollectionExtensionsTest.cs new file mode 100644 index 0000000000..d78581655c --- /dev/null +++ b/src/Localization/Localization/test/LocalizationServiceCollectionExtensionsTest.cs @@ -0,0 +1,68 @@ +// 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.Linq; +using Microsoft.Extensions.Localization; +using Microsoft.Extensions.Options; +using Xunit; + +namespace Microsoft.Extensions.DependencyInjection +{ + public class LocalizationServiceCollectionExtensionsTest + { + [Fact] + public void AddLocalization_AddsNeededServices() + { + // Arrange + var collection = new ServiceCollection(); + + // Act + LocalizationServiceCollectionExtensions.AddLocalizationServices(collection); + + // Assert + AssertContainsSingle(collection, typeof(IStringLocalizerFactory), typeof(ResourceManagerStringLocalizerFactory)); + AssertContainsSingle(collection, typeof(IStringLocalizer<>), typeof(StringLocalizer<>)); + } + + [Fact] + public void AddLocalizationWithLocalizationOptions_AddsNeededServices() + { + // Arrange + var collection = new ServiceCollection(); + + // Act + LocalizationServiceCollectionExtensions.AddLocalizationServices( + collection, + options => options.ResourcesPath = "Resources"); + + AssertContainsSingle(collection, typeof(IStringLocalizerFactory), typeof(ResourceManagerStringLocalizerFactory)); + AssertContainsSingle(collection, typeof(IStringLocalizer<>), typeof(StringLocalizer<>)); + } + + private void AssertContainsSingle( + IServiceCollection services, + Type serviceType, + Type implementationType) + { + var matches = services + .Where(sd => + sd.ServiceType == serviceType && + sd.ImplementationType == implementationType) + .ToArray(); + + if (matches.Length == 0) + { + Assert.True( + false, + $"Could not find an instance of {implementationType} registered as {serviceType}"); + } + else if (matches.Length > 1) + { + Assert.True( + false, + $"Found multiple instances of {implementationType} registered as {serviceType}"); + } + } + } +} diff --git a/src/Localization/Localization/test/Microsoft.Extensions.Localization.Tests.csproj b/src/Localization/Localization/test/Microsoft.Extensions.Localization.Tests.csproj new file mode 100644 index 0000000000..4628ff7c0f --- /dev/null +++ b/src/Localization/Localization/test/Microsoft.Extensions.Localization.Tests.csproj @@ -0,0 +1,14 @@ + + + + $(StandardTestTfms) + + + + + + + + + + diff --git a/src/Localization/Localization/test/ResourceManagerStringLocalizerFactoryTest.cs b/src/Localization/Localization/test/ResourceManagerStringLocalizerFactoryTest.cs new file mode 100644 index 0000000000..7a18c0e4bd --- /dev/null +++ b/src/Localization/Localization/test/ResourceManagerStringLocalizerFactoryTest.cs @@ -0,0 +1,296 @@ +// 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.IO; +using System.Reflection; +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Logging.Abstractions; +using Microsoft.Extensions.Options; +using Moq; +using Xunit; + +// This namespace intentionally matches the default assembly namespace. +namespace Microsoft.Extensions.Localization.Tests +{ + public class TestResourceManagerStringLocalizerFactory : ResourceManagerStringLocalizerFactory + { + private ResourceLocationAttribute _resourceLocationAttribute; + + private RootNamespaceAttribute _rootNamespaceAttribute; + + public Assembly Assembly { get; private set; } + public string BaseName { get; private set; } + + public TestResourceManagerStringLocalizerFactory( + IOptions localizationOptions, + ResourceLocationAttribute resourceLocationAttribute, + RootNamespaceAttribute rootNamespaceAttribute, + ILoggerFactory loggerFactory) + : base(localizationOptions, loggerFactory) + { + _resourceLocationAttribute = resourceLocationAttribute; + _rootNamespaceAttribute = rootNamespaceAttribute; + } + + protected override ResourceLocationAttribute GetResourceLocationAttribute(Assembly assembly) + { + return _resourceLocationAttribute; + } + + protected override RootNamespaceAttribute GetRootNamespaceAttribute(Assembly assembly) + { + return _rootNamespaceAttribute; + } + + protected override ResourceManagerStringLocalizer CreateResourceManagerStringLocalizer(Assembly assembly, string baseName) + { + BaseName = baseName; + Assembly = assembly; + + return base.CreateResourceManagerStringLocalizer(assembly, baseName); + } + } + + public class ResourceManagerStringLocalizerFactoryTest + { + [Fact] + public void Create_OverloadsProduceSameResult() + { + // Arrange + var locOptions = new LocalizationOptions(); + var options = new Mock>(); + options.Setup(o => o.Value).Returns(locOptions); + + var resourceLocationAttribute = new ResourceLocationAttribute(Path.Combine("My", "Resources")); + var loggerFactory = NullLoggerFactory.Instance; + var typeFactory = new TestResourceManagerStringLocalizerFactory( + options.Object, + resourceLocationAttribute, + rootNamespaceAttribute: null, + loggerFactory: loggerFactory); + var stringFactory = new TestResourceManagerStringLocalizerFactory( + options.Object, + resourceLocationAttribute, + rootNamespaceAttribute: null, + loggerFactory: loggerFactory); + var type = typeof(ResourceManagerStringLocalizerFactoryTest); + var assemblyName = new AssemblyName(type.GetTypeInfo().Assembly.FullName); + + // Act + typeFactory.Create(type); + stringFactory.Create(type.Name, assemblyName.Name); + + // Assert + Assert.Equal(typeFactory.BaseName, stringFactory.BaseName); + Assert.Equal(typeFactory.Assembly.FullName, stringFactory.Assembly.FullName); + } + + [Fact] + public void Create_FromType_ReturnsCachedResultForSameType() + { + // Arrange + var locOptions = new LocalizationOptions(); + var options = new Mock>(); + options.Setup(o => o.Value).Returns(locOptions); + var loggerFactory = NullLoggerFactory.Instance; + var factory = new ResourceManagerStringLocalizerFactory(localizationOptions: options.Object, loggerFactory: loggerFactory); + + // Act + var result1 = factory.Create(typeof(ResourceManagerStringLocalizerFactoryTest)); + var result2 = factory.Create(typeof(ResourceManagerStringLocalizerFactoryTest)); + + // Assert + Assert.Same(result1, result2); + } + + [Fact] + public void Create_FromType_ReturnsNewResultForDifferentType() + { + // Arrange + var locOptions = new LocalizationOptions(); + var options = new Mock>(); + options.Setup(o => o.Value).Returns(locOptions); + var loggerFactory = NullLoggerFactory.Instance; + var factory = new ResourceManagerStringLocalizerFactory(localizationOptions: options.Object, loggerFactory: loggerFactory); + + // Act + var result1 = factory.Create(typeof(ResourceManagerStringLocalizerFactoryTest)); + var result2 = factory.Create(typeof(LocalizationOptions)); + + // Assert + Assert.NotSame(result1, result2); + } + + [Fact] + public void Create_ResourceLocationAttribute_RootNamespaceIgnoredWhenNoLocation() + { + // Arrange + var locOptions = new LocalizationOptions(); + var options = new Mock>(); + options.Setup(o => o.Value).Returns(locOptions); + var loggerFactory = NullLoggerFactory.Instance; + + var resourcePath = Path.Combine("My", "Resources"); + var rootNamespace = "MyNamespace"; + var rootNamespaceAttribute = new RootNamespaceAttribute(rootNamespace); + + var typeFactory = new TestResourceManagerStringLocalizerFactory( + options.Object, + resourceLocationAttribute: null, + rootNamespaceAttribute: rootNamespaceAttribute, + loggerFactory: loggerFactory); + + var type = typeof(ResourceManagerStringLocalizerFactoryTest); + // Act + typeFactory.Create(type); + + // Assert + Assert.Equal($"Microsoft.Extensions.Localization.Tests.ResourceManagerStringLocalizerFactoryTest", typeFactory.BaseName); + } + + [Fact] + public void Create_ResourceLocationAttribute_UsesRootNamespace() + { + // Arrange + var locOptions = new LocalizationOptions(); + var options = new Mock>(); + options.Setup(o => o.Value).Returns(locOptions); + var loggerFactory = NullLoggerFactory.Instance; + + var resourcePath = Path.Combine("My", "Resources"); + var rootNamespace = "MyNamespace"; + var resourceLocationAttribute = new ResourceLocationAttribute(resourcePath); + var rootNamespaceAttribute = new RootNamespaceAttribute(rootNamespace); + + var typeFactory = new TestResourceManagerStringLocalizerFactory( + options.Object, + resourceLocationAttribute, + rootNamespaceAttribute, + loggerFactory); + + var type = typeof(ResourceManagerStringLocalizerFactoryTest); + // Act + typeFactory.Create(type); + + // Assert + Assert.Equal($"MyNamespace.My.Resources.ResourceManagerStringLocalizerFactoryTest", typeFactory.BaseName); + } + + [Fact] + public void Create_FromType_ResourcesPathDirectorySeperatorToDot() + { + // Arrange + var locOptions = new LocalizationOptions(); + locOptions.ResourcesPath = Path.Combine("My", "Resources"); + var options = new Mock>(); + options.Setup(o => o.Value).Returns(locOptions); + var loggerFactory = NullLoggerFactory.Instance; + var factory = new TestResourceManagerStringLocalizerFactory( + options.Object, + resourceLocationAttribute: null, + rootNamespaceAttribute: null, + loggerFactory: loggerFactory); + + // Act + factory.Create(typeof(ResourceManagerStringLocalizerFactoryTest)); + + // Assert + Assert.Equal("Microsoft.Extensions.Localization.Tests.My.Resources." + nameof(ResourceManagerStringLocalizerFactoryTest), factory.BaseName); + } + + [Fact] + public void Create_FromNameLocation_ReturnsCachedResultForSameNameLocation() + { + // Arrange + var locOptions = new LocalizationOptions(); + var options = new Mock>(); + options.Setup(o => o.Value).Returns(locOptions); + var loggerFactory = NullLoggerFactory.Instance; + var factory = new ResourceManagerStringLocalizerFactory(localizationOptions: options.Object, loggerFactory: loggerFactory); + var location = typeof(ResourceManagerStringLocalizer).GetTypeInfo().Assembly.FullName; + + // Act + var result1 = factory.Create("baseName", location); + var result2 = factory.Create("baseName", location); + + // Assert + Assert.Same(result1, result2); + } + + [Fact] + public void Create_FromNameLocation_ReturnsNewResultForDifferentName() + { + // Arrange + var locOptions = new LocalizationOptions(); + var options = new Mock>(); + options.Setup(o => o.Value).Returns(locOptions); + var loggerFactory = NullLoggerFactory.Instance; + var factory = new ResourceManagerStringLocalizerFactory(localizationOptions: options.Object, loggerFactory: loggerFactory); + var location = typeof(ResourceManagerStringLocalizer).GetTypeInfo().Assembly.FullName; + + // Act + var result1 = factory.Create("baseName1", location); + var result2 = factory.Create("baseName2", location); + + // Assert + Assert.NotSame(result1, result2); + } + + [Fact] + public void Create_FromNameLocation_ReturnsNewResultForDifferentLocation() + { + // Arrange + var locOptions = new LocalizationOptions(); + var options = new Mock>(); + options.Setup(o => o.Value).Returns(locOptions); + var loggerFactory = NullLoggerFactory.Instance; + var factory = new ResourceManagerStringLocalizerFactory(localizationOptions: options.Object, loggerFactory: loggerFactory); + var location1 = new AssemblyName(typeof(ResourceManagerStringLocalizer).GetTypeInfo().Assembly.FullName).Name; + var location2 = new AssemblyName(typeof(ResourceManagerStringLocalizerFactoryTest).GetTypeInfo().Assembly.FullName).Name; + + // Act + var result1 = factory.Create("baseName", location1); + var result2 = factory.Create("baseName", location2); + + // Assert + Assert.NotSame(result1, result2); + } + + [Fact] + public void Create_FromNameLocation_ResourcesPathDirectorySeparatorToDot() + { + // Arrange + var locOptions = new LocalizationOptions(); + locOptions.ResourcesPath = Path.Combine("My", "Resources"); + var options = new Mock>(); + options.Setup(o => o.Value).Returns(locOptions); + var loggerFactory = NullLoggerFactory.Instance; + var factory = new TestResourceManagerStringLocalizerFactory( + options.Object, + resourceLocationAttribute: null, + rootNamespaceAttribute: null, + loggerFactory: loggerFactory); + + // Act + var result1 = factory.Create("baseName", location: "Microsoft.Extensions.Localization.Tests"); + + // Assert + Assert.Equal("Microsoft.Extensions.Localization.Tests.My.Resources.baseName", factory.BaseName); + } + + [Fact] + public void Create_FromNameLocation_NullLocationThrows() + { + // Arrange + var locOptions = new LocalizationOptions(); + var options = new Mock>(); + options.Setup(o => o.Value).Returns(locOptions); + var loggerFactory = NullLoggerFactory.Instance; + var factory = new ResourceManagerStringLocalizerFactory(localizationOptions: options.Object, loggerFactory: loggerFactory); + + // Act & Assert + Assert.Throws(() => factory.Create("baseName", location: null)); + } + } +} diff --git a/src/Localization/Localization/test/ResourceManagerStringLocalizerTest.cs b/src/Localization/Localization/test/ResourceManagerStringLocalizerTest.cs new file mode 100644 index 0000000000..ff7bfa9933 --- /dev/null +++ b/src/Localization/Localization/test/ResourceManagerStringLocalizerTest.cs @@ -0,0 +1,299 @@ +// 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.Globalization; +using System.IO; +using System.Linq; +using System.Reflection; +using System.Resources; +using Microsoft.AspNetCore.Testing; +using Microsoft.Extensions.Localization.Internal; +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Logging.Testing; +using Xunit; + +namespace Microsoft.Extensions.Localization +{ + public class ResourceManagerStringLocalizerTest + { + [Fact] + public void EnumeratorCachesCultureWalkForSameAssembly() + { + // Arrange + var resourceNamesCache = new ResourceNamesCache(); + var baseName = "test"; + var resourceAssembly = new TestAssemblyWrapper(); + var resourceManager = new TestResourceManager(baseName, resourceAssembly); + var resourceStreamManager = new TestResourceStringProvider( + resourceNamesCache, + resourceManager, + resourceAssembly.Assembly, + baseName); + var logger = Logger; + var localizer1 = new ResourceManagerStringLocalizer(resourceManager, + resourceStreamManager, + baseName, + resourceNamesCache, + logger); + var localizer2 = new ResourceManagerStringLocalizer(resourceManager, + resourceStreamManager, + baseName, + resourceNamesCache, + logger); + + // Act + for (var i = 0; i < 5; i++) + { + localizer1.GetAllStrings().ToList(); + localizer2.GetAllStrings().ToList(); + } + + // Assert + var expectedCallCount = GetCultureInfoDepth(CultureInfo.CurrentUICulture); + Assert.Equal(expectedCallCount, resourceAssembly.ManifestResourceStreamCallCount); + } + + [Fact] + public void EnumeratorCacheIsScopedByAssembly() + { + // Arrange + var resourceNamesCache = new ResourceNamesCache(); + var baseName = "test"; + var resourceAssembly1 = new TestAssemblyWrapper(typeof(ResourceManagerStringLocalizerTest)); + var resourceAssembly2 = new TestAssemblyWrapper(typeof(ResourceManagerStringLocalizer)); + var resourceManager1 = new TestResourceManager(baseName, resourceAssembly1); + var resourceManager2 = new TestResourceManager(baseName, resourceAssembly2); + var resourceStreamManager1 = new TestResourceStringProvider(resourceNamesCache, resourceManager1, resourceAssembly1.Assembly, baseName); + var resourceStreamManager2 = new TestResourceStringProvider(resourceNamesCache, resourceManager2, resourceAssembly2.Assembly, baseName); + var logger = Logger; + var localizer1 = new ResourceManagerStringLocalizer( + resourceManager1, + resourceStreamManager1, + baseName, + resourceNamesCache, + logger); + var localizer2 = new ResourceManagerStringLocalizer( + resourceManager2, + resourceStreamManager2, + baseName, + resourceNamesCache, + logger); + + // Act + localizer1.GetAllStrings().ToList(); + localizer2.GetAllStrings().ToList(); + + // Assert + var expectedCallCount = GetCultureInfoDepth(CultureInfo.CurrentUICulture); + Assert.Equal(expectedCallCount, resourceAssembly1.ManifestResourceStreamCallCount); + Assert.Equal(expectedCallCount, resourceAssembly2.ManifestResourceStreamCallCount); + } + + [Fact] + public void GetString_PopulatesSearchedLocationOnLocalizedString() + { + // Arrange + var baseName = "Resources.TestResource"; + var resourceNamesCache = new ResourceNamesCache(); + var resourceAssembly = new TestAssemblyWrapper(); + var resourceManager = new TestResourceManager(baseName, resourceAssembly); + var resourceStreamManager = new TestResourceStringProvider(resourceNamesCache, resourceManager, resourceAssembly.Assembly, baseName); + var logger = Logger; + var localizer = new ResourceManagerStringLocalizer( + resourceManager, + resourceStreamManager, + baseName, + resourceNamesCache, + logger); + + // Act + var value = localizer["name"]; + + // Assert + Assert.Equal("Resources.TestResource", value.SearchedLocation); + } + + [Fact] + [ReplaceCulture("en-US", "en-US")] + public void GetString_LogsLocationSearched() + { + // Arrange + var baseName = "Resources.TestResource"; + var resourceNamesCache = new ResourceNamesCache(); + var resourceAssembly = new TestAssemblyWrapper(); + var resourceManager = new TestResourceManager(baseName, resourceAssembly); + var resourceStreamManager = new TestResourceStringProvider(resourceNamesCache, resourceManager, resourceAssembly.Assembly, baseName); + var logger = Logger; + + var localizer = new ResourceManagerStringLocalizer( + resourceManager, + resourceStreamManager, + baseName, + resourceNamesCache, + logger); + + // Act + var value = localizer["a key!"]; + + // Assert + var write = Assert.Single(Sink.Writes); + Assert.Equal("ResourceManagerStringLocalizer searched for 'a key!' in 'Resources.TestResource' with culture 'en-US'.", write.State.ToString()); + } + + [Theory] + [InlineData(true)] + [InlineData(false)] + public void ResourceManagerStringLocalizer_GetAllStrings_ReturnsExpectedValue(bool includeParentCultures) + { + // Arrange + var baseName = "test"; + var resourceNamesCache = new ResourceNamesCache(); + var resourceAssembly = new TestAssemblyWrapper(); + var resourceManager = new TestResourceManager(baseName, resourceAssembly); + var resourceStreamManager = new TestResourceStringProvider(resourceNamesCache, resourceManager, resourceAssembly.Assembly, baseName); + var logger = Logger; + var localizer = new ResourceManagerStringLocalizer( + resourceManager, + resourceStreamManager, + baseName, + resourceNamesCache, + logger); + + // Act + // We have to access the result so it evaluates. + var strings = localizer.GetAllStrings(includeParentCultures).ToList(); + + // Assert + var value = Assert.Single(strings); + Assert.Equal("TestName", value.Value); + } + + [Theory] + [InlineData(true)] + [InlineData(false)] + public void ResourceManagerStringLocalizer_GetAllStrings_MissingResourceThrows(bool includeParentCultures) + { + // Arrange + var resourceNamesCache = new ResourceNamesCache(); + var baseName = "testington"; + var resourceAssembly = new TestAssemblyWrapper(); + resourceAssembly.HasResources = false; + var resourceManager = new TestResourceManager(baseName, resourceAssembly); + var logger = Logger; + + var localizer = new ResourceManagerWithCultureStringLocalizer( + resourceManager, + resourceAssembly.Assembly, + baseName, + resourceNamesCache, + CultureInfo.CurrentCulture, + logger); + + // Act & Assert + var exception = Assert.Throws(() => + { + // We have to access the result so it evaluates. + localizer.GetAllStrings(includeParentCultures).ToArray(); + }); + + var expectedTries = includeParentCultures ? 3 : 1; + var expected = includeParentCultures + ? "No manifests exist for the current culture." + : $"The manifest 'testington.{CultureInfo.CurrentCulture}.resources' was not found."; + Assert.Equal(expected, exception.Message); + Assert.Equal(expectedTries, resourceAssembly.ManifestResourceStreamCallCount); + } + + private static Stream MakeResourceStream() + { + var stream = new MemoryStream(); + var resourceWriter = new ResourceWriter(stream); + resourceWriter.AddResource("TestName", "value"); + resourceWriter.Generate(); + stream.Position = 0; + return stream; + } + + private static int GetCultureInfoDepth(CultureInfo culture) + { + var result = 0; + var currentCulture = culture; + + while (true) + { + result++; + + if (currentCulture == currentCulture.Parent) + { + break; + } + + currentCulture = currentCulture.Parent; + } + + return result; + } + + + private TestSink Sink { get; } = new TestSink(); + + private ILogger Logger => new TestLoggerFactory(Sink, enabled: true).CreateLogger(); + + public class TestResourceManager : ResourceManager + { + private AssemblyWrapper _assemblyWrapper; + + public TestResourceManager(string baseName, AssemblyWrapper assemblyWrapper) + : base(baseName, assemblyWrapper.Assembly) + { + _assemblyWrapper = assemblyWrapper; + } + + public override string GetString(string name, CultureInfo culture) => null; + + public override ResourceSet GetResourceSet(CultureInfo culture, bool createIfNotExists, bool tryParents) + { + var resourceStream = _assemblyWrapper.GetManifestResourceStream(BaseName); + + return resourceStream != null ? new ResourceSet(resourceStream) : null; + } + } + + public class TestResourceStringProvider : ResourceManagerStringProvider + { + public TestResourceStringProvider( + IResourceNamesCache resourceCache, + TestResourceManager resourceManager, + Assembly assembly, + string resourceBaseName) + : base(resourceCache, resourceManager, assembly, resourceBaseName) + { + } + } + + public class TestAssemblyWrapper : AssemblyWrapper + { + public TestAssemblyWrapper() + : this(typeof(TestAssemblyWrapper)) + { + } + + public TestAssemblyWrapper(Type type) + : base(type.GetTypeInfo().Assembly) + { + } + + public bool HasResources { get; set; } = true; + + public int ManifestResourceStreamCallCount { get; private set; } + + public override Stream GetManifestResourceStream(string name) + { + ManifestResourceStreamCallCount++; + + return HasResources ? MakeResourceStream() : null; + } + } + } +} From 090119859b15a49b54b8f637842eebf0ff1a42a0 Mon Sep 17 00:00:00 2001 From: Nate McMaster Date: Mon, 3 Dec 2018 16:22:29 -0800 Subject: [PATCH 0023/1101] Update targets and tasks to build Localization projects \n\nCommit migrated from https://github.com/dotnet/extensions/commit/22f5402e13ad2c5cdab081a5480077a69c5cc2e2 --- .../test/Microsoft.Extensions.Localization.Tests.csproj | 1 - 1 file changed, 1 deletion(-) diff --git a/src/Localization/Localization/test/Microsoft.Extensions.Localization.Tests.csproj b/src/Localization/Localization/test/Microsoft.Extensions.Localization.Tests.csproj index 4628ff7c0f..e7a47a1a6e 100644 --- a/src/Localization/Localization/test/Microsoft.Extensions.Localization.Tests.csproj +++ b/src/Localization/Localization/test/Microsoft.Extensions.Localization.Tests.csproj @@ -5,7 +5,6 @@ - From e7c4b678f99ac20ee46c9fef0fb930d0d2810e81 Mon Sep 17 00:00:00 2001 From: Nate McMaster Date: Tue, 4 Dec 2018 10:40:35 -0800 Subject: [PATCH 0024/1101] Reorganize source code in preparation to move into aspnet/Extensions Prior to reorganization, this source code was found in https://github.com/aspnet/Localization/tree/dotnet/extensions@60a3d57a3e8b8b6d69232815a9596b5a495a7d0b \n\nCommit migrated from https://github.com/dotnet/extensions/commit/b885414e8a44e777a9dd1170416bb143c24ed524 --- .../test/Microsoft.Extensions.Localization.Tests.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Localization/Localization/test/Microsoft.Extensions.Localization.Tests.csproj b/src/Localization/Localization/test/Microsoft.Extensions.Localization.Tests.csproj index 4628ff7c0f..30f071feae 100644 --- a/src/Localization/Localization/test/Microsoft.Extensions.Localization.Tests.csproj +++ b/src/Localization/Localization/test/Microsoft.Extensions.Localization.Tests.csproj @@ -1,7 +1,7 @@  - $(StandardTestTfms) + netcoreapp3.0;net461 From 57cd32129b52caeb329478c67c9c973f5d94efa1 Mon Sep 17 00:00:00 2001 From: Arthur Vickers Date: Fri, 30 Nov 2018 14:43:22 -0800 Subject: [PATCH 0025/1101] Added tests that can be implemented for any provider to ensure consistency Partly to allow me to get a better understanding of the code, partly so we can have a better idea that all the providers work in a consistent manner. Part of https://github.com/aspnet/Configuration/issues/559 I will file issues for things found. \n\nCommit migrated from https://github.com/dotnet/extensions/commit/1878d16ecd5165849cd6a78ce708e499b71477ca --- .../ConfigurationProviderCommandLineTest.cs | 43 ++ .../test/ConfigurationProviderTestBase.cs | 552 ++++++++++++++++++ .../test/KeyPerFileTests.cs | 4 +- ...ions.Configuration.KeyPerFile.Tests.csproj | 4 + 4 files changed, 602 insertions(+), 1 deletion(-) create mode 100644 src/Configuration.KeyPerFile/test/ConfigurationProviderCommandLineTest.cs create mode 100644 src/Configuration.KeyPerFile/test/ConfigurationProviderTestBase.cs diff --git a/src/Configuration.KeyPerFile/test/ConfigurationProviderCommandLineTest.cs b/src/Configuration.KeyPerFile/test/ConfigurationProviderCommandLineTest.cs new file mode 100644 index 0000000000..066aecf337 --- /dev/null +++ b/src/Configuration.KeyPerFile/test/ConfigurationProviderCommandLineTest.cs @@ -0,0 +1,43 @@ +// 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.Collections.Generic; +using System.Linq; +using Microsoft.Extensions.Configuration.Test; +using Microsoft.Extensions.FileProviders; + +namespace Microsoft.Extensions.Configuration.KeyPerFile.Test +{ + public class ConfigurationProviderCommandLineTest : ConfigurationProviderTestBase + { + protected override (IConfigurationProvider Provider, Action Initializer) LoadThroughProvider( + TestSection testConfig) + { + var testFiles = new List(); + SectionToTestFiles(testFiles, "", testConfig); + + var provider = new KeyPerFileConfigurationProvider( + new KeyPerFileConfigurationSource + { + Optional = true, + FileProvider = new TestFileProvider(testFiles.ToArray()) + }); + + return (provider, () => { }); + } + + private void SectionToTestFiles(List testFiles, string sectionName, TestSection section) + { + foreach (var tuple in section.Values.SelectMany(e => e.Value.Expand(e.Key))) + { + testFiles.Add(new TestFile(sectionName + tuple.Key, tuple.Value)); + } + + foreach (var tuple in section.Sections) + { + SectionToTestFiles(testFiles, sectionName + tuple.Key + "__", tuple.Section); + } + } + } +} diff --git a/src/Configuration.KeyPerFile/test/ConfigurationProviderTestBase.cs b/src/Configuration.KeyPerFile/test/ConfigurationProviderTestBase.cs new file mode 100644 index 0000000000..c18bea8d28 --- /dev/null +++ b/src/Configuration.KeyPerFile/test/ConfigurationProviderTestBase.cs @@ -0,0 +1,552 @@ +// 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.Collections.Generic; +using System.Linq; +using Microsoft.Extensions.Configuration.Memory; +using Xunit; + +namespace Microsoft.Extensions.Configuration.Test +{ + public abstract class ConfigurationProviderTestBase + { + [Fact] + public virtual void Load_from_single_provider() + { + AssertConfig(BuildConfigRoot(LoadThroughProvider(TestSection.TestConfig))); + } + + [Fact] + public virtual void Combine_after_other_provider() + { + AssertConfig( + BuildConfigRoot( + LoadUsingMemoryProvider(TestSection.MissingSection2Config), + LoadThroughProvider(TestSection.MissingSection4Config))); + } + + [Fact] + public virtual void Combine_before_other_provider() + { + AssertConfig( + BuildConfigRoot( + LoadThroughProvider(TestSection.MissingSection2Config), + LoadUsingMemoryProvider(TestSection.MissingSection4Config))); + } + + [Fact] + public virtual void Second_provider_overrides_values_from_first() + { + AssertConfig( + BuildConfigRoot( + LoadUsingMemoryProvider(TestSection.NoValuesTestConfig), + LoadThroughProvider(TestSection.TestConfig))); + } + + [Fact] + public virtual void Combining_from_multiple_providers_is_case_insensitive() + { + AssertConfig( + BuildConfigRoot( + LoadUsingMemoryProvider(TestSection.DifferentCasedTestConfig), + LoadThroughProvider(TestSection.TestConfig))); + } + + [Fact] + public virtual void Load_from_single_provider_with_duplicates_throws() + { + AssertFormatOrArgumentException( + () => BuildConfigRoot(LoadThroughProvider(TestSection.DuplicatesTestConfig))); + } + + [Fact] + public virtual void Load_from_single_provider_with_differing_case_duplicates_throws() + { + AssertFormatOrArgumentException( + () => BuildConfigRoot(LoadThroughProvider(TestSection.DuplicatesDifferentCaseTestConfig))); + } + + private void AssertFormatOrArgumentException(Action test) + { + Exception caught = null; + try + { + test(); + } + catch (Exception e) + { + caught = e; + } + + Assert.True(caught is ArgumentException + || caught is FormatException); + } + + [Fact] + public virtual void Bind_to_object() + { + var configuration = BuildConfigRoot(LoadThroughProvider(TestSection.TestConfig)); + + var options = configuration.Get(); + + Assert.Equal("Value1", options.Key1); + Assert.Equal("Value12", options.Section1.Key2); + Assert.Equal("Value123", options.Section1.Section2.Key3); + Assert.Equal("Value344", options.Section3.Section4.Key4); + Assert.Equal(new[] { "ArrayValue0", "ArrayValue1", "ArrayValue2" }, options.Section1.Section2.Key3a); + } + + public class AsOptions + { + public string Key1 { get; set; } + + public Section1AsOptions Section1 { get; set; } + public Section3AsOptions Section3 { get; set; } + } + + public class Section1AsOptions + { + public string Key2 { get; set; } + + public Section2AsOptions Section2 { get; set; } + } + + public class Section2AsOptions + { + public string Key3 { get; set; } + public string[] Key3a { get; set; } + } + + public class Section3AsOptions + { + public Section4AsOptions Section4 { get; set; } + } + + public class Section4AsOptions + { + public string Key4 { get; set; } + } + + protected virtual void AssertConfig(IConfigurationRoot config) + { + Assert.Equal("Value1", config["Key1"], StringComparer.InvariantCultureIgnoreCase); + Assert.Equal("Value12", config["Section1:Key2"], StringComparer.InvariantCultureIgnoreCase); + Assert.Equal("Value123", config["Section1:Section2:Key3"], StringComparer.InvariantCultureIgnoreCase); + Assert.Equal("ArrayValue0", config["Section1:Section2:Key3a:0"], StringComparer.InvariantCultureIgnoreCase); + Assert.Equal("ArrayValue1", config["Section1:Section2:Key3a:1"], StringComparer.InvariantCultureIgnoreCase); + Assert.Equal("ArrayValue2", config["Section1:Section2:Key3a:2"], StringComparer.InvariantCultureIgnoreCase); + Assert.Equal("Value344", config["Section3:Section4:Key4"], StringComparer.InvariantCultureIgnoreCase); + + var section1 = config.GetSection("Section1"); + Assert.Equal("Value12", section1["Key2"], StringComparer.InvariantCultureIgnoreCase); + Assert.Equal("Value123", section1["Section2:Key3"], StringComparer.InvariantCultureIgnoreCase); + Assert.Equal("ArrayValue0", section1["Section2:Key3a:0"], StringComparer.InvariantCultureIgnoreCase); + Assert.Equal("ArrayValue1", section1["Section2:Key3a:1"], StringComparer.InvariantCultureIgnoreCase); + Assert.Equal("ArrayValue2", section1["Section2:Key3a:2"], StringComparer.InvariantCultureIgnoreCase); + Assert.Equal("Section1", section1.Path, StringComparer.InvariantCultureIgnoreCase); + Assert.Null(section1.Value); + + var section2 = config.GetSection("Section1:Section2"); + Assert.Equal("Value123", section2["Key3"], StringComparer.InvariantCultureIgnoreCase); + Assert.Equal("ArrayValue0", section2["Key3a:0"], StringComparer.InvariantCultureIgnoreCase); + Assert.Equal("ArrayValue1", section2["Key3a:1"], StringComparer.InvariantCultureIgnoreCase); + Assert.Equal("ArrayValue2", section2["Key3a:2"], StringComparer.InvariantCultureIgnoreCase); + Assert.Equal("Section1:Section2", section2.Path, StringComparer.InvariantCultureIgnoreCase); + Assert.Null(section2.Value); + + section2 = section1.GetSection("Section2"); + Assert.Equal("Value123", section2["Key3"], StringComparer.InvariantCultureIgnoreCase); + Assert.Equal("ArrayValue0", section2["Key3a:0"], StringComparer.InvariantCultureIgnoreCase); + Assert.Equal("ArrayValue1", section2["Key3a:1"], StringComparer.InvariantCultureIgnoreCase); + Assert.Equal("ArrayValue2", section2["Key3a:2"], StringComparer.InvariantCultureIgnoreCase); + Assert.Equal("Section1:Section2", section2.Path, StringComparer.InvariantCultureIgnoreCase); + Assert.Null(section2.Value); + + var section4 = config.GetSection("Section3:Section4"); + Assert.Equal("Value344", section4["Key4"], StringComparer.InvariantCultureIgnoreCase); + Assert.Equal("Section3:Section4", section4.Path, StringComparer.InvariantCultureIgnoreCase); + Assert.Null(section4.Value); + + section4 = config.GetSection("Section3").GetSection("Section4"); + Assert.Equal("Value344", section4["Key4"], StringComparer.InvariantCultureIgnoreCase); + Assert.Equal("Section3:Section4", section4.Path, StringComparer.InvariantCultureIgnoreCase); + Assert.Null(section4.Value); + + var sections = config.GetChildren().ToList(); + + Assert.Equal(3, sections.Count); + + Assert.Equal("Key1", sections[0].Key, StringComparer.InvariantCultureIgnoreCase); + Assert.Equal("Key1", sections[0].Path, StringComparer.InvariantCultureIgnoreCase); + Assert.Equal("Value1", sections[0].Value, StringComparer.InvariantCultureIgnoreCase); + + Assert.Equal("Section1", sections[1].Key, StringComparer.InvariantCultureIgnoreCase); + Assert.Equal("Section1", sections[1].Path, StringComparer.InvariantCultureIgnoreCase); + Assert.Null(sections[1].Value); + + Assert.Equal("Section3", sections[2].Key, StringComparer.InvariantCultureIgnoreCase); + Assert.Equal("Section3", sections[2].Path, StringComparer.InvariantCultureIgnoreCase); + Assert.Null(sections[2].Value); + + sections = section1.GetChildren().ToList(); + + Assert.Equal(2, sections.Count); + + Assert.Equal("Key2", sections[0].Key, StringComparer.InvariantCultureIgnoreCase); + Assert.Equal("Section1:Key2", sections[0].Path, StringComparer.InvariantCultureIgnoreCase); + Assert.Equal("Value12", sections[0].Value, StringComparer.InvariantCultureIgnoreCase); + + Assert.Equal("Section2", sections[1].Key, StringComparer.InvariantCultureIgnoreCase); + Assert.Equal("Section1:Section2", sections[1].Path, StringComparer.InvariantCultureIgnoreCase); + Assert.Null(sections[1].Value); + } + + protected abstract (IConfigurationProvider Provider, Action Initializer) LoadThroughProvider(TestSection testConfig); + + protected virtual IConfigurationRoot BuildConfigRoot( + params (IConfigurationProvider Provider, Action Initializer)[] providers) + { + var root = new ConfigurationRoot(providers.Select(e => e.Provider).ToList()); + + foreach (var initializer in providers.Select(e => e.Initializer)) + { + initializer(); + } + + return root; + } + + protected static (IConfigurationProvider Provider, Action Initializer) LoadUsingMemoryProvider(TestSection testConfig) + { + var values = new List>(); + SectionToValues(testConfig, "", values); + + return (new MemoryConfigurationProvider( + new MemoryConfigurationSource + { + InitialData = values + }), + () => { }); + } + + protected static void SectionToValues( + TestSection section, + string sectionName, + IList> values) + { + foreach (var tuple in section.Values.SelectMany(e => e.Value.Expand(e.Key))) + { + values.Add(new KeyValuePair(sectionName + tuple.Key, tuple.Value)); + } + + foreach (var tuple in section.Sections) + { + SectionToValues( + tuple.Section, + sectionName + tuple.Key + ":", + values); + } + } + + protected class TestKeyValue + { + public object Value { get; } + + public TestKeyValue(string value) + { + Value = value; + } + + public TestKeyValue(string[] values) + { + Value = values; + } + + public static implicit operator TestKeyValue(string value) => new TestKeyValue(value); + public static implicit operator TestKeyValue(string[] values) => new TestKeyValue(values); + + public string[] AsArray => Value as string[]; + + public string AsString => Value as string; + + public IEnumerable<(string Key, string Value)> Expand(string key) + { + if (AsString != null) + { + yield return (key, AsString); + } + else + { + for (var i = 0; i < AsArray.Length; i++) + { + yield return ($"{key}:{i}", AsArray[i]); + } + } + } + } + + protected class TestSection + { + public IEnumerable<(string Key, TestKeyValue Value)> Values { get; set; } + = Enumerable.Empty<(string, TestKeyValue)>(); + + public IEnumerable<(string Key, TestSection Section)> Sections { get; set; } + = Enumerable.Empty<(string, TestSection)>(); + + public static TestSection TestConfig { get; } + = new TestSection + { + Values = new[] { ("Key1", (TestKeyValue)"Value1") }, + Sections = new[] + { + ("Section1", new TestSection + { + Values = new[] {("Key2", (TestKeyValue)"Value12")}, + Sections = new[] + { + ("Section2", new TestSection + { + Values = new[] + { + ("Key3", (TestKeyValue)"Value123"), + ("Key3a", (TestKeyValue)new[] {"ArrayValue0", "ArrayValue1", "ArrayValue2"}), + }, + }) + } + }), + ("Section3", new TestSection + { + Sections = new[] + { + ("Section4", new TestSection + { + Values = new[] {("Key4", (TestKeyValue)"Value344")}, + }) + } + }), + + } + }; + + public static TestSection NoValuesTestConfig { get; } + = new TestSection + { + Values = new[] { ("Key1", (TestKeyValue)"------") }, + Sections = new[] + { + ("Section1", new TestSection + { + Values = new[] {("Key2", (TestKeyValue)"-------")}, + Sections = new[] + { + ("Section2", new TestSection + { + Values = new[] + { + ("Key3", (TestKeyValue)"-----"), + ("Key3a", (TestKeyValue)new[] {"-----------", "-----------", "-----------"}), + }, + }) + } + }), + ("Section3", new TestSection + { + Sections = new[] + { + ("Section4", new TestSection + { + Values = new[] {("Key4", (TestKeyValue)"--------")}, + }) + } + }), + + } + }; + + public static TestSection MissingSection2Config { get; } + = new TestSection + { + Values = new[] { ("Key1", (TestKeyValue)"Value1") }, + Sections = new[] + { + ("Section1", new TestSection + { + Values = new[] {("Key2", (TestKeyValue)"Value12")}, + }), + ("Section3", new TestSection + { + Sections = new[] + { + ("Section4", new TestSection + { + Values = new[] {("Key4", (TestKeyValue)"Value344")}, + }) + } + }), + + } + }; + + + public static TestSection MissingSection4Config { get; } + = new TestSection + { + Values = new[] { ("Key1", (TestKeyValue)"Value1") }, + Sections = new[] + { + ("Section1", new TestSection + { + Values = new[] {("Key2", (TestKeyValue)"Value12")}, + Sections = new[] + { + ("Section2", new TestSection + { + Values = new[] + { + ("Key3", (TestKeyValue)"Value123"), + ("Key3a", (TestKeyValue)new[] {"ArrayValue0", "ArrayValue1", "ArrayValue2"}), + }, + }) + } + }), + ("Section3", new TestSection + { + }), + + } + }; + + public static TestSection DifferentCasedTestConfig { get; } + = new TestSection + { + Values = new[] { ("KeY1", (TestKeyValue)"Value1") }, + Sections = new[] + { + ("SectioN1", new TestSection + { + Values = new[] {("KeY2", (TestKeyValue)"Value12")}, + Sections = new[] + { + ("SectioN2", new TestSection + { + Values = new[] + { + ("KeY3", (TestKeyValue)"Value123"), + ("KeY3a", (TestKeyValue)new[] {"ArrayValue0", "ArrayValue1", "ArrayValue2"}), + }, + }) + } + }), + ("SectioN3", new TestSection + { + Sections = new[] + { + ("SectioN4", new TestSection + { + Values = new[] {("KeY4", (TestKeyValue)"Value344")}, + }) + } + }), + + } + }; + + public static TestSection DuplicatesTestConfig { get; } + = new TestSection + { + Values = new[] + { + ("Key1", (TestKeyValue)"Value1"), + ("Key1", (TestKeyValue)"Value1") + }, + Sections = new[] + { + ("Section1", new TestSection + { + Values = new[] {("Key2", (TestKeyValue)"Value12")}, + Sections = new[] + { + ("Section2", new TestSection + { + Values = new[] + { + ("Key3", (TestKeyValue)"Value123"), + ("Key3a", (TestKeyValue)new[] {"ArrayValue0", "ArrayValue1", "ArrayValue2"}), + }, + }), + ("Section2", new TestSection + { + Values = new[] + { + ("Key3", (TestKeyValue)"Value123"), + ("Key3a", (TestKeyValue)new[] {"ArrayValue0", "ArrayValue1", "ArrayValue2"}), + }, + }) + + } + }), + ("Section3", new TestSection + { + Sections = new[] + { + ("Section4", new TestSection + { + Values = new[] {("Key4", (TestKeyValue)"Value344")}, + }) + } + }), + + } + }; + + public static TestSection DuplicatesDifferentCaseTestConfig { get; } + = new TestSection + { + Values = new[] + { + ("Key1", (TestKeyValue)"Value1"), + ("KeY1", (TestKeyValue)"Value1") + }, + Sections = new[] + { + ("Section1", new TestSection + { + Values = new[] {("Key2", (TestKeyValue)"Value12")}, + Sections = new[] + { + ("Section2", new TestSection + { + Values = new[] + { + ("Key3", (TestKeyValue)"Value123"), + ("Key3a", (TestKeyValue)new[] {"ArrayValue0", "ArrayValue1", "ArrayValue2"}), + }, + }), + ("SectioN2", new TestSection + { + Values = new[] + { + ("KeY3", (TestKeyValue)"Value123"), + ("KeY3a", (TestKeyValue)new[] {"ArrayValue0", "ArrayValue1", "ArrayValue2"}), + }, + }) + + } + }), + ("Section3", new TestSection + { + Sections = new[] + { + ("Section4", new TestSection + { + Values = new[] {("Key4", (TestKeyValue)"Value344")}, + }) + } + }), + + } + }; + } + } +} diff --git a/src/Configuration.KeyPerFile/test/KeyPerFileTests.cs b/src/Configuration.KeyPerFile/test/KeyPerFileTests.cs index d409c0eab0..fac0e839e8 100644 --- a/src/Configuration.KeyPerFile/test/KeyPerFileTests.cs +++ b/src/Configuration.KeyPerFile/test/KeyPerFileTests.cs @@ -1,8 +1,10 @@ +// 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.Collections; using System.Collections.Generic; using System.IO; -using System.Linq; using System.Text; using Microsoft.Extensions.FileProviders; using Microsoft.Extensions.Primitives; diff --git a/src/Configuration.KeyPerFile/test/Microsoft.Extensions.Configuration.KeyPerFile.Tests.csproj b/src/Configuration.KeyPerFile/test/Microsoft.Extensions.Configuration.KeyPerFile.Tests.csproj index 634056a345..e78f4fd190 100644 --- a/src/Configuration.KeyPerFile/test/Microsoft.Extensions.Configuration.KeyPerFile.Tests.csproj +++ b/src/Configuration.KeyPerFile/test/Microsoft.Extensions.Configuration.KeyPerFile.Tests.csproj @@ -4,6 +4,10 @@ $(StandardTestTfms) + + + + From 317b4bb4eb3284f9f7051a73bbb7f70997572195 Mon Sep 17 00:00:00 2001 From: Nate McMaster Date: Thu, 13 Dec 2018 09:49:07 -0800 Subject: [PATCH 0026/1101] Create README.md for the localization folder (dotnet/extensions#683) \n\nCommit migrated from https://github.com/dotnet/extensions/commit/c841b4f9e0ad4b3724136dcfde35a8b1ad71b537 --- src/Localization/README.md | 6 ++++++ 1 file changed, 6 insertions(+) create mode 100644 src/Localization/README.md diff --git a/src/Localization/README.md b/src/Localization/README.md new file mode 100644 index 0000000000..dc6894b6e0 --- /dev/null +++ b/src/Localization/README.md @@ -0,0 +1,6 @@ +Localization +============ + +These projects provide abstractions for localizing resources in .NET applications. + +The ASP.NET Core implementation of localization can be found in https://github.com/aspnet/AspNetCore/tree/master/src/Middleware/Localization. From a053c7e1a46352e0b97c1bf2f42b98145e901651 Mon Sep 17 00:00:00 2001 From: Nate McMaster Date: Thu, 13 Dec 2018 10:33:33 -0800 Subject: [PATCH 0027/1101] Stop producing Microsoft.Extensions.Process.Sources This was only used in the aspnet/AspNetCore repo. so the source for this project is moving to that repo \n\nCommit migrated from https://github.com/dotnet/extensions/commit/98522c18fbce3570c108618db7ced3f5ed74a7b1 --- src/Shared/Process/ProcessHelper.cs | 113 ---------------------------- 1 file changed, 113 deletions(-) delete mode 100644 src/Shared/Process/ProcessHelper.cs diff --git a/src/Shared/Process/ProcessHelper.cs b/src/Shared/Process/ProcessHelper.cs deleted file mode 100644 index cf42a7e3a7..0000000000 --- a/src/Shared/Process/ProcessHelper.cs +++ /dev/null @@ -1,113 +0,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.Collections.Generic; -using System.Diagnostics; -using System.IO; -using System.Runtime.InteropServices; - -namespace Microsoft.Extensions.Internal -{ - internal static class ProcessExtensions - { - private static readonly bool _isWindows = RuntimeInformation.IsOSPlatform(OSPlatform.Windows); - private static readonly TimeSpan _defaultTimeout = TimeSpan.FromSeconds(30); - - public static void KillTree(this Process process) - { - process.KillTree(_defaultTimeout); - } - - public static void KillTree(this Process process, TimeSpan timeout) - { - string stdout; - if (_isWindows) - { - RunProcessAndWaitForExit( - "taskkill", - $"/T /F /PID {process.Id}", - timeout, - out stdout); - } - else - { - var children = new HashSet(); - GetAllChildIdsUnix(process.Id, children, timeout); - foreach (var childId in children) - { - KillProcessUnix(childId, timeout); - } - KillProcessUnix(process.Id, timeout); - } - } - - private static void GetAllChildIdsUnix(int parentId, ISet children, TimeSpan timeout) - { - string stdout; - var exitCode = RunProcessAndWaitForExit( - "pgrep", - $"-P {parentId}", - timeout, - out stdout); - - if (exitCode == 0 && !string.IsNullOrEmpty(stdout)) - { - using (var reader = new StringReader(stdout)) - { - while (true) - { - var text = reader.ReadLine(); - if (text == null) - { - return; - } - - int id; - if (int.TryParse(text, out id)) - { - children.Add(id); - // Recursively get the children - GetAllChildIdsUnix(id, children, timeout); - } - } - } - } - } - - private static void KillProcessUnix(int processId, TimeSpan timeout) - { - string stdout; - RunProcessAndWaitForExit( - "kill", - $"-TERM {processId}", - timeout, - out stdout); - } - - private static int RunProcessAndWaitForExit(string fileName, string arguments, TimeSpan timeout, out string stdout) - { - var startInfo = new ProcessStartInfo - { - FileName = fileName, - Arguments = arguments, - RedirectStandardOutput = true, - UseShellExecute = false - }; - - var process = Process.Start(startInfo); - - stdout = null; - if (process.WaitForExit((int)timeout.TotalMilliseconds)) - { - stdout = process.StandardOutput.ReadToEnd(); - } - else - { - process.Kill(); - } - - return process.ExitCode; - } - } -} From 2d001bb58319e4aac567caae36d08d2606f91b1e Mon Sep 17 00:00:00 2001 From: Nate McMaster Date: Thu, 13 Dec 2018 10:33:47 -0800 Subject: [PATCH 0028/1101] Stop producing Microsoft.AspNetCore.Certificates.Generation.Sources This was only used in the aspnet/AspNetCore repo. so the source for this project is moving to that repo \n\nCommit migrated from https://github.com/dotnet/extensions/commit/9290dc1dbccdb6c6063970a0c1ac5f384cc95785 --- .../CertificateManager.cs | 720 ------------------ .../CertificatePurpose.cs | 12 - .../Directory.Build.props | 7 - .../EnsureCertificateResult.cs | 20 - .../Shared.Tests/CertificateManagerTests.cs | 304 -------- 5 files changed, 1063 deletions(-) delete mode 100644 src/Shared/CertificateGeneration/CertificateManager.cs delete mode 100644 src/Shared/CertificateGeneration/CertificatePurpose.cs delete mode 100644 src/Shared/CertificateGeneration/Directory.Build.props delete mode 100644 src/Shared/CertificateGeneration/EnsureCertificateResult.cs delete mode 100644 src/Shared/test/Shared.Tests/CertificateManagerTests.cs diff --git a/src/Shared/CertificateGeneration/CertificateManager.cs b/src/Shared/CertificateGeneration/CertificateManager.cs deleted file mode 100644 index 4e2a0a9964..0000000000 --- a/src/Shared/CertificateGeneration/CertificateManager.cs +++ /dev/null @@ -1,720 +0,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.Collections.Generic; -using System.Diagnostics; -using System.IO; -using System.Linq; -using System.Runtime.InteropServices; -using System.Security.Cryptography; -using System.Security.Cryptography.X509Certificates; -using System.Text; -using System.Text.RegularExpressions; - -namespace Microsoft.AspNetCore.Certificates.Generation -{ - internal class CertificateManager - { - public const string AspNetHttpsOid = "1.3.6.1.4.1.311.84.1.1"; - public const string AspNetHttpsOidFriendlyName = "ASP.NET Core HTTPS development certificate"; - - public const string AspNetIdentityOid = "1.3.6.1.4.1.311.84.1.2"; - public const string AspNetIdentityOidFriendlyName = "ASP.NET Core Identity Json Web Token signing development certificate"; - - private const string ServerAuthenticationEnhancedKeyUsageOid = "1.3.6.1.5.5.7.3.1"; - private const string ServerAuthenticationEnhancedKeyUsageOidFriendlyName = "Server Authentication"; - - private const string LocalhostHttpsDnsName = "localhost"; - private const string LocalhostHttpsDistinguishedName = "CN=" + LocalhostHttpsDnsName; - - private const string IdentityDistinguishedName = "CN=Microsoft.AspNetCore.Identity.Signing"; - - public const int RSAMinimumKeySizeInBits = 2048; - - private static readonly TimeSpan MaxRegexTimeout = TimeSpan.FromMinutes(1); - private const string CertificateSubjectRegex = "CN=(.*[^,]+).*"; - private const string MacOSSystemKeyChain = "/Library/Keychains/System.keychain"; - private static readonly string MacOSUserKeyChain = Environment.GetEnvironmentVariable("HOME") + "/Library/Keychains/login.keychain-db"; - private const string MacOSFindCertificateCommandLine = "security"; -#if NETCOREAPP2_0 || NETCOREAPP2_1 - private static readonly string MacOSFindCertificateCommandLineArgumentsFormat = "find-certificate -c {0} -a -Z -p " + MacOSSystemKeyChain; -#endif - private const string MacOSFindCertificateOutputRegex = "SHA-1 hash: ([0-9A-Z]+)"; - private const string MacOSRemoveCertificateTrustCommandLine = "sudo"; - private const string MacOSRemoveCertificateTrustCommandLineArgumentsFormat = "security remove-trusted-cert -d {0}"; - private const string MacOSDeleteCertificateCommandLine = "sudo"; - private const string MacOSDeleteCertificateCommandLineArgumentsFormat = "security delete-certificate -Z {0} {1}"; - private const string MacOSTrustCertificateCommandLine = "sudo"; -#if NETCOREAPP2_0 || NETCOREAPP2_1 - private static readonly string MacOSTrustCertificateCommandLineArguments = "security add-trusted-cert -d -r trustRoot -k " + MacOSSystemKeyChain + " "; -#endif - private const int UserCancelledErrorCode = 1223; - - public IList ListCertificates( - CertificatePurpose purpose, - StoreName storeName, - StoreLocation location, - bool isValid, - bool requireExportable = true) - { - var certificates = new List(); - try - { - using (var store = new X509Store(storeName, location)) - { - store.Open(OpenFlags.ReadOnly); - certificates.AddRange(store.Certificates.OfType()); - IEnumerable matchingCertificates = certificates; - switch (purpose) - { - case CertificatePurpose.All: - matchingCertificates = matchingCertificates - .Where(c => HasOid(c, AspNetHttpsOid) || HasOid(c, AspNetIdentityOid)); - break; - case CertificatePurpose.HTTPS: - matchingCertificates = matchingCertificates - .Where(c => HasOid(c, AspNetHttpsOid)); - break; - case CertificatePurpose.Signing: - matchingCertificates = matchingCertificates - .Where(c => HasOid(c, AspNetIdentityOid)); - break; - default: - break; - } - if (isValid) - { - // Ensure the certificate hasn't expired, has a private key and its exportable - // (for container/unix scenarios). - var now = DateTimeOffset.Now; - matchingCertificates = matchingCertificates - .Where(c => c.NotBefore <= now && - now <= c.NotAfter && - (!requireExportable || !RuntimeInformation.IsOSPlatform(OSPlatform.Windows) || IsExportable(c))); - } - - // We need to enumerate the certificates early to prevent dispoisng issues. - matchingCertificates = matchingCertificates.ToList(); - - var certificatesToDispose = certificates.Except(matchingCertificates); - DisposeCertificates(certificatesToDispose); - - store.Close(); - - return (IList)matchingCertificates; - } - } - catch - { - DisposeCertificates(certificates); - certificates.Clear(); - return certificates; - } - - bool HasOid(X509Certificate2 certificate, string oid) => - certificate.Extensions.OfType() - .Any(e => string.Equals(oid, e.Oid.Value, StringComparison.Ordinal)); -#if !XPLAT - bool IsExportable(X509Certificate2 c) => - ((c.GetRSAPrivateKey() is RSACryptoServiceProvider rsaPrivateKey && - rsaPrivateKey.CspKeyContainerInfo.Exportable) || - (c.GetRSAPrivateKey() is RSACng cngPrivateKey && - cngPrivateKey.Key.ExportPolicy == CngExportPolicies.AllowExport)); -#else - // Only check for RSA CryptoServiceProvider and do not fail in XPlat tooling as - // System.Security.Cryptography.Cng is not pat of the shared framework and we don't - // want to bring the dependency in on CLI scenarios. This functionality will be used - // on CLI scenarios as part of the first run experience, so checking the exportability - // of the certificate is not important. - bool IsExportable(X509Certificate2 c) => - ((c.GetRSAPrivateKey() is RSACryptoServiceProvider rsaPrivateKey && - rsaPrivateKey.CspKeyContainerInfo.Exportable) || !(c.GetRSAPrivateKey() is RSACryptoServiceProvider)); -#endif - } - - private void DisposeCertificates(IEnumerable disposables) - { - foreach (var disposable in disposables) - { - try - { - disposable.Dispose(); - } - catch - { - } - } - } - -#if NETCOREAPP2_0 || NETCOREAPP2_1 - - public X509Certificate2 CreateAspNetCoreHttpsDevelopmentCertificate(DateTimeOffset notBefore, DateTimeOffset notAfter, string subjectOverride) - { - var subject = new X500DistinguishedName(subjectOverride ?? LocalhostHttpsDistinguishedName); - var extensions = new List(); - var sanBuilder = new SubjectAlternativeNameBuilder(); - sanBuilder.AddDnsName(LocalhostHttpsDnsName); - - var keyUsage = new X509KeyUsageExtension(X509KeyUsageFlags.KeyEncipherment, critical: true); - var enhancedKeyUsage = new X509EnhancedKeyUsageExtension( - new OidCollection() { - new Oid( - ServerAuthenticationEnhancedKeyUsageOid, - ServerAuthenticationEnhancedKeyUsageOidFriendlyName) - }, - critical: true); - - var basicConstraints = new X509BasicConstraintsExtension( - certificateAuthority: false, - hasPathLengthConstraint: false, - pathLengthConstraint: 0, - critical: true); - - var aspNetHttpsExtension = new X509Extension( - new AsnEncodedData( - new Oid(AspNetHttpsOid, AspNetHttpsOidFriendlyName), - Encoding.ASCII.GetBytes(AspNetHttpsOidFriendlyName)), - critical: false); - - extensions.Add(basicConstraints); - extensions.Add(keyUsage); - extensions.Add(enhancedKeyUsage); - extensions.Add(sanBuilder.Build(critical: true)); - extensions.Add(aspNetHttpsExtension); - - var certificate = CreateSelfSignedCertificate(subject, extensions, notBefore, notAfter); - if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) - { - certificate.FriendlyName = AspNetHttpsOidFriendlyName; - } - - return certificate; - } - - public X509Certificate2 CreateApplicationTokenSigningDevelopmentCertificate(DateTimeOffset notBefore, DateTimeOffset notAfter, string subjectOverride) - { - var subject = new X500DistinguishedName(subjectOverride ?? IdentityDistinguishedName); - var extensions = new List(); - - var keyUsage = new X509KeyUsageExtension(X509KeyUsageFlags.DigitalSignature, critical: true); - var enhancedKeyUsage = new X509EnhancedKeyUsageExtension( - new OidCollection() { - new Oid( - ServerAuthenticationEnhancedKeyUsageOid, - ServerAuthenticationEnhancedKeyUsageOidFriendlyName) - }, - critical: true); - - var basicConstraints = new X509BasicConstraintsExtension( - certificateAuthority: false, - hasPathLengthConstraint: false, - pathLengthConstraint: 0, - critical: true); - - var aspNetIdentityExtension = new X509Extension( - new AsnEncodedData( - new Oid(AspNetIdentityOid, AspNetIdentityOidFriendlyName), - Encoding.ASCII.GetBytes(AspNetIdentityOidFriendlyName)), - critical: false); - - extensions.Add(basicConstraints); - extensions.Add(keyUsage); - extensions.Add(enhancedKeyUsage); - extensions.Add(aspNetIdentityExtension); - - var certificate = CreateSelfSignedCertificate(subject, extensions, notBefore, notAfter); - if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) - { - certificate.FriendlyName = AspNetIdentityOidFriendlyName; - } - - return certificate; - } - - public X509Certificate2 CreateSelfSignedCertificate( - X500DistinguishedName subject, - IEnumerable extensions, - DateTimeOffset notBefore, - DateTimeOffset notAfter) - { - var key = CreateKeyMaterial(RSAMinimumKeySizeInBits); - - var request = new CertificateRequest(subject, key, HashAlgorithmName.SHA256, RSASignaturePadding.Pkcs1); - foreach (var extension in extensions) - { - request.CertificateExtensions.Add(extension); - } - - return request.CreateSelfSigned(notBefore, notAfter); - - RSA CreateKeyMaterial(int minimumKeySize) - { - var rsa = RSA.Create(minimumKeySize); - if (rsa.KeySize < minimumKeySize) - { - throw new InvalidOperationException($"Failed to create a key with a size of {minimumKeySize} bits"); - } - - return rsa; - } - } - - public X509Certificate2 SaveCertificateInStore(X509Certificate2 certificate, StoreName name, StoreLocation location) - { - var imported = certificate; - if (!RuntimeInformation.IsOSPlatform(OSPlatform.OSX)) - { - // On non OSX systems we need to export the certificate and import it so that the transient - // key that we generated gets persisted. - var export = certificate.Export(X509ContentType.Pkcs12, ""); - imported = new X509Certificate2(export, "", X509KeyStorageFlags.PersistKeySet | X509KeyStorageFlags.Exportable); - Array.Clear(export, 0, export.Length); - } - - if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) - { - imported.FriendlyName = certificate.FriendlyName; - } - - using (var store = new X509Store(name, location)) - { - store.Open(OpenFlags.ReadWrite); - store.Add(imported); - store.Close(); - }; - - return imported; - } - - public void ExportCertificate(X509Certificate2 certificate, string path, bool includePrivateKey, string password) - { - if (Path.GetDirectoryName(path) != "") - { - Directory.CreateDirectory(Path.GetDirectoryName(path)); - } - - if (includePrivateKey) - { - var bytes = certificate.Export(X509ContentType.Pkcs12, password); - try - { - File.WriteAllBytes(path, bytes); - } - finally - { - Array.Clear(bytes, 0, bytes.Length); - } - } - else - { - var bytes = certificate.Export(X509ContentType.Cert); - File.WriteAllBytes(path, bytes); - } - } - - public void TrustCertificate(X509Certificate2 certificate) - { - // Strip certificate of the private key if any. - var publicCertificate = new X509Certificate2(certificate.Export(X509ContentType.Cert)); - - if (!IsTrusted(publicCertificate)) - { - if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) - { - TrustCertificateOnWindows(certificate, publicCertificate); - } - else if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX)) - { - TrustCertificateOnMac(publicCertificate); - } - } - } - - private void TrustCertificateOnMac(X509Certificate2 publicCertificate) - { - var tmpFile = Path.GetTempFileName(); - try - { - ExportCertificate(publicCertificate, tmpFile, includePrivateKey: false, password: null); - using (var process = Process.Start(MacOSTrustCertificateCommandLine, MacOSTrustCertificateCommandLineArguments + tmpFile)) - { - process.WaitForExit(); - if (process.ExitCode != 0) - { - throw new InvalidOperationException("There was an error trusting the certificate."); - } - } - } - finally - { - try - { - if (File.Exists(tmpFile)) - { - File.Delete(tmpFile); - } - } - catch - { - // We don't care if we can't delete the temp file. - } - } - } - - private static void TrustCertificateOnWindows(X509Certificate2 certificate, X509Certificate2 publicCertificate) - { - publicCertificate.FriendlyName = certificate.FriendlyName; - - using (var store = new X509Store(StoreName.Root, StoreLocation.CurrentUser)) - { - store.Open(OpenFlags.ReadWrite); - try - { - store.Add(publicCertificate); - } - catch (CryptographicException exception) when (exception.HResult == UserCancelledErrorCode) - { - throw new UserCancelledTrustException(); - } - store.Close(); - }; - } - - public bool IsTrusted(X509Certificate2 certificate) - { - if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) - { - return ListCertificates(CertificatePurpose.HTTPS, StoreName.Root, StoreLocation.CurrentUser, isValid: true, requireExportable: false) - .Any(c => c.Thumbprint == certificate.Thumbprint); - } - else if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX)) - { - var subjectMatch = Regex.Match(certificate.Subject, CertificateSubjectRegex, RegexOptions.Singleline, MaxRegexTimeout); - if (!subjectMatch.Success) - { - throw new InvalidOperationException($"Can't determine the subject for the certificate with subject '{certificate.Subject}'."); - } - var subject = subjectMatch.Groups[1].Value; - using (var checkTrustProcess = Process.Start(new ProcessStartInfo( - MacOSFindCertificateCommandLine, - string.Format(MacOSFindCertificateCommandLineArgumentsFormat, subject)) - { - RedirectStandardOutput = true - })) - { - var output = checkTrustProcess.StandardOutput.ReadToEnd(); - checkTrustProcess.WaitForExit(); - var matches = Regex.Matches(output, MacOSFindCertificateOutputRegex, RegexOptions.Multiline, MaxRegexTimeout); - var hashes = matches.OfType().Select(m => m.Groups[1].Value).ToList(); - return hashes.Any(h => string.Equals(h, certificate.Thumbprint, StringComparison.Ordinal)); - } - } - else - { - return false; - } - } - - public void CleanupHttpsCertificates(string subject = LocalhostHttpsDistinguishedName) - { - CleanupCertificates(CertificatePurpose.HTTPS, subject); - } - - public void CleanupCertificates(CertificatePurpose purpose, string subject) - { - // On OS X we don't have a good way to manage trusted certificates in the system keychain - // so we do everything by invoking the native toolchain. - // This has some limitations, like for example not being able to identify our custom OID extension. For that - // matter, when we are cleaning up certificates on the machine, we start by removing the trusted certificates. - // To do this, we list the certificates that we can identify on the current user personal store and we invoke - // the native toolchain to remove them from the sytem keychain. Once we have removed the trusted certificates, - // we remove the certificates from the local user store to finish up the cleanup. - var certificates = ListCertificates(purpose, StoreName.My, StoreLocation.CurrentUser, isValid: false); - foreach (var certificate in certificates) - { - RemoveCertificate(certificate, RemoveLocations.All); - } - } - - public void RemoveAllCertificates(CertificatePurpose purpose, StoreName storeName, StoreLocation storeLocation, string subject = null) - { - var certificates = RuntimeInformation.IsOSPlatform(OSPlatform.OSX) ? - ListCertificates(purpose, StoreName.My, StoreLocation.CurrentUser, isValid: false) : - ListCertificates(purpose, storeName, storeLocation, isValid: false); - var certificatesWithName = subject == null ? certificates : certificates.Where(c => c.Subject == subject); - - var removeLocation = storeName == StoreName.My ? RemoveLocations.Local : RemoveLocations.Trusted; - - foreach (var certificate in certificates) - { - RemoveCertificate(certificate, removeLocation); - } - - DisposeCertificates(certificates); - } - - private void RemoveCertificate(X509Certificate2 certificate, RemoveLocations locations) - { - switch (locations) - { - case RemoveLocations.Undefined: - throw new InvalidOperationException($"'{nameof(RemoveLocations.Undefined)}' is not a valid location."); - case RemoveLocations.Local: - RemoveCertificateFromUserStore(certificate); - break; - case RemoveLocations.Trusted when !RuntimeInformation.IsOSPlatform(OSPlatform.Linux): - RemoveCertificateFromTrustedRoots(certificate); - break; - case RemoveLocations.All: - if (!RuntimeInformation.IsOSPlatform(OSPlatform.Linux)) - { - RemoveCertificateFromTrustedRoots(certificate); - } - RemoveCertificateFromUserStore(certificate); - break; - default: - throw new InvalidOperationException("Invalid location."); - } - } - - private static void RemoveCertificateFromUserStore(X509Certificate2 certificate) - { - using (var store = new X509Store(StoreName.My, StoreLocation.CurrentUser)) - { - store.Open(OpenFlags.ReadWrite); - var matching = store.Certificates - .OfType() - .Single(c => c.SerialNumber == certificate.SerialNumber); - - store.Remove(matching); - store.Close(); - } - } - - private void RemoveCertificateFromTrustedRoots(X509Certificate2 certificate) - { - if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) - { - using (var store = new X509Store(StoreName.Root, StoreLocation.CurrentUser)) - { - store.Open(OpenFlags.ReadWrite); - var matching = store.Certificates - .OfType() - .Single(c => c.SerialNumber == certificate.SerialNumber); - - store.Remove(matching); - store.Close(); - } - } - else - { - if (IsTrusted(certificate)) // On OSX this check just ensures its on the system keychain - { - try - { - RemoveCertificateTrustRule(certificate); - } - catch - { - // We don't care if we fail to remove the trust rule if - // for some reason the certificate became untrusted. - // The delete command will fail if the certificate is - // trusted. - } - RemoveCertificateFromKeyChain(MacOSSystemKeyChain, certificate); - } - } - } - - private static void RemoveCertificateTrustRule(X509Certificate2 certificate) - { - var certificatePath = Path.GetTempFileName(); - try - { - var certBytes = certificate.Export(X509ContentType.Cert); - File.WriteAllBytes(certificatePath, certBytes); - var processInfo = new ProcessStartInfo( - MacOSRemoveCertificateTrustCommandLine, - string.Format( - MacOSRemoveCertificateTrustCommandLineArgumentsFormat, - certificatePath - )); - using (var process = Process.Start(processInfo)) - { - process.WaitForExit(); - } - } - finally - { - try - { - if (File.Exists(certificatePath)) - { - File.Delete(certificatePath); - } - } - catch - { - // We don't care about failing to do clean-up on a temp file. - } - } - } - - private static void RemoveCertificateFromKeyChain(string keyChain, X509Certificate2 certificate) - { - var processInfo = new ProcessStartInfo( - MacOSDeleteCertificateCommandLine, - string.Format( - MacOSDeleteCertificateCommandLineArgumentsFormat, - certificate.Thumbprint.ToUpperInvariant(), - keyChain - )) - { - RedirectStandardOutput = true, - RedirectStandardError = true - }; - - using (var process = Process.Start(processInfo)) - { - var output = process.StandardOutput.ReadToEnd() + process.StandardError.ReadToEnd(); - process.WaitForExit(); - - if (process.ExitCode != 0) - { - throw new InvalidOperationException($@"There was an error removing the certificate with thumbprint '{certificate.Thumbprint}'. - -{output}"); - } - } - } - - public EnsureCertificateResult EnsureAspNetCoreHttpsDevelopmentCertificate( - DateTimeOffset notBefore, - DateTimeOffset notAfter, - string path = null, - bool trust = false, - bool includePrivateKey = false, - string password = null, - string subject = LocalhostHttpsDistinguishedName) - { - return EnsureValidCertificateExists(notBefore, notAfter, CertificatePurpose.HTTPS, path, trust, includePrivateKey, password, subject); - } - - public EnsureCertificateResult EnsureAspNetCoreApplicationTokensDevelopmentCertificate( - DateTimeOffset notBefore, - DateTimeOffset notAfter, - string path = null, - bool trust = false, - bool includePrivateKey = false, - string password = null, - string subject = IdentityDistinguishedName) - { - return EnsureValidCertificateExists(notBefore, notAfter, CertificatePurpose.Signing, path, trust, includePrivateKey, password, subject); - } - - public EnsureCertificateResult EnsureValidCertificateExists( - DateTimeOffset notBefore, - DateTimeOffset notAfter, - CertificatePurpose purpose, - string path = null, - bool trust = false, - bool includePrivateKey = false, - string password = null, - string subjectOverride = null) - { - if (purpose == CertificatePurpose.All) - { - throw new ArgumentException("The certificate must have a specific purpose."); - } - - var certificates = ListCertificates(purpose, StoreName.My, StoreLocation.CurrentUser, isValid: true).Concat( - ListCertificates(purpose, StoreName.My, StoreLocation.LocalMachine, isValid: true)); - - certificates = subjectOverride == null ? certificates : certificates.Where(c => c.Subject == subjectOverride); - - var result = EnsureCertificateResult.Succeeded; - - X509Certificate2 certificate = null; - if (certificates.Count() > 0) - { - certificate = certificates.FirstOrDefault(); - result = EnsureCertificateResult.ValidCertificatePresent; - } - else - { - try - { - switch (purpose) - { - case CertificatePurpose.All: - throw new InvalidOperationException("The certificate must have a specific purpose."); - case CertificatePurpose.HTTPS: - certificate = CreateAspNetCoreHttpsDevelopmentCertificate(notBefore, notAfter, subjectOverride); - break; - case CertificatePurpose.Signing: - certificate = CreateApplicationTokenSigningDevelopmentCertificate(notBefore, notAfter, subjectOverride); - break; - default: - throw new InvalidOperationException("The certificate must have a purpose."); - } - } - catch - { - return EnsureCertificateResult.ErrorCreatingTheCertificate; - } - - try - { - certificate = SaveCertificateInStore(certificate, StoreName.My, StoreLocation.CurrentUser); - } - catch - { - return EnsureCertificateResult.ErrorSavingTheCertificateIntoTheCurrentUserPersonalStore; - } - } - if (path != null) - { - try - { - ExportCertificate(certificate, path, includePrivateKey, password); - } - catch - { - return EnsureCertificateResult.ErrorExportingTheCertificate; - } - } - - if ((RuntimeInformation.IsOSPlatform(OSPlatform.Windows) || RuntimeInformation.IsOSPlatform(OSPlatform.OSX)) && trust) - { - try - { - TrustCertificate(certificate); - } - catch (UserCancelledTrustException) - { - return EnsureCertificateResult.UserCancelledTrustStep; - } - catch - { - return EnsureCertificateResult.FailedToTrustTheCertificate; - } - } - - return result; - } - - private class UserCancelledTrustException : Exception - { - } - - private enum RemoveLocations - { - Undefined, - Local, - Trusted, - All - } -#endif - } -} \ No newline at end of file diff --git a/src/Shared/CertificateGeneration/CertificatePurpose.cs b/src/Shared/CertificateGeneration/CertificatePurpose.cs deleted file mode 100644 index 1ad1a6d79b..0000000000 --- a/src/Shared/CertificateGeneration/CertificatePurpose.cs +++ /dev/null @@ -1,12 +0,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. - -namespace Microsoft.AspNetCore.Certificates.Generation -{ - internal enum CertificatePurpose - { - All, - HTTPS, - Signing - } -} \ No newline at end of file diff --git a/src/Shared/CertificateGeneration/Directory.Build.props b/src/Shared/CertificateGeneration/Directory.Build.props deleted file mode 100644 index f3f6fc4b35..0000000000 --- a/src/Shared/CertificateGeneration/Directory.Build.props +++ /dev/null @@ -1,7 +0,0 @@ - - - - - Microsoft.AspNetCore.Certificates.Generation.Sources - - diff --git a/src/Shared/CertificateGeneration/EnsureCertificateResult.cs b/src/Shared/CertificateGeneration/EnsureCertificateResult.cs deleted file mode 100644 index d3c86ce05d..0000000000 --- a/src/Shared/CertificateGeneration/EnsureCertificateResult.cs +++ /dev/null @@ -1,20 +0,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. - -#if NETCOREAPP2_0 || NETCOREAPP2_1 - -namespace Microsoft.AspNetCore.Certificates.Generation -{ - internal enum EnsureCertificateResult - { - Succeeded = 1, - ValidCertificatePresent, - ErrorCreatingTheCertificate, - ErrorSavingTheCertificateIntoTheCurrentUserPersonalStore, - ErrorExportingTheCertificate, - FailedToTrustTheCertificate, - UserCancelledTrustStep - } -} - -#endif \ No newline at end of file diff --git a/src/Shared/test/Shared.Tests/CertificateManagerTests.cs b/src/Shared/test/Shared.Tests/CertificateManagerTests.cs deleted file mode 100644 index 613f5c966f..0000000000 --- a/src/Shared/test/Shared.Tests/CertificateManagerTests.cs +++ /dev/null @@ -1,304 +0,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. - -#if NETCOREAPP2_0 || NETCOREAPP2_1 - -using System; -using System.IO; -using System.Linq; -using System.Runtime.InteropServices; -using System.Security.Cryptography; -using System.Security.Cryptography.X509Certificates; -using System.Text; -using Xunit; -using Xunit.Abstractions; - -namespace Microsoft.AspNetCore.Certificates.Generation.Tests -{ - public class CertificateManagerTests - { - public CertificateManagerTests(ITestOutputHelper output) - { - Output = output; - } - - public const string TestCertificateSubject = "CN=aspnet.test"; - - public ITestOutputHelper Output { get; } - - [Fact] - public void EnsureCreateHttpsCertificate_CreatesACertificate_WhenThereAreNoHttpsCertificates() - { - try - { - // Arrange - const string CertificateName = nameof(EnsureCreateHttpsCertificate_CreatesACertificate_WhenThereAreNoHttpsCertificates) + ".cer"; - var manager = new CertificateManager(); - - manager.RemoveAllCertificates(CertificatePurpose.HTTPS, StoreName.My, StoreLocation.CurrentUser, TestCertificateSubject); - if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) - { - manager.RemoveAllCertificates(CertificatePurpose.HTTPS, StoreName.Root, StoreLocation.CurrentUser, TestCertificateSubject); - } - - // Act - DateTimeOffset now = DateTimeOffset.UtcNow; - now = new DateTimeOffset(now.Year, now.Month, now.Day, now.Hour, now.Minute, now.Second, 0, now.Offset); - var result = manager.EnsureAspNetCoreHttpsDevelopmentCertificate(now, now.AddYears(1), CertificateName, trust: false, subject: TestCertificateSubject); - - // Assert - Assert.Equal(EnsureCertificateResult.Succeeded, result); - Assert.True(File.Exists(CertificateName)); - - var exportedCertificate = new X509Certificate2(File.ReadAllBytes(CertificateName)); - Assert.NotNull(exportedCertificate); - Assert.False(exportedCertificate.HasPrivateKey); - - var httpsCertificates = manager.ListCertificates(CertificatePurpose.HTTPS, StoreName.My, StoreLocation.CurrentUser, isValid: false); - var httpsCertificate = Assert.Single(httpsCertificates, c => c.Subject == TestCertificateSubject); - Assert.True(httpsCertificate.HasPrivateKey); - Assert.Equal(TestCertificateSubject, httpsCertificate.Subject); - Assert.Equal(TestCertificateSubject, httpsCertificate.Issuer); - Assert.Equal("sha256RSA", httpsCertificate.SignatureAlgorithm.FriendlyName); - Assert.Equal("1.2.840.113549.1.1.11", httpsCertificate.SignatureAlgorithm.Value); - - Assert.Equal(now.LocalDateTime, httpsCertificate.NotBefore); - Assert.Equal(now.AddYears(1).LocalDateTime, httpsCertificate.NotAfter); - Assert.Contains( - httpsCertificate.Extensions.OfType(), - e => e is X509BasicConstraintsExtension basicConstraints && - basicConstraints.Critical == true && - basicConstraints.CertificateAuthority == false && - basicConstraints.HasPathLengthConstraint == false && - basicConstraints.PathLengthConstraint == 0); - - Assert.Contains( - httpsCertificate.Extensions.OfType(), - e => e is X509KeyUsageExtension keyUsage && - keyUsage.Critical == true && - keyUsage.KeyUsages == X509KeyUsageFlags.KeyEncipherment); - - Assert.Contains( - httpsCertificate.Extensions.OfType(), - e => e is X509EnhancedKeyUsageExtension enhancedKeyUsage && - enhancedKeyUsage.Critical == true && - enhancedKeyUsage.EnhancedKeyUsages.OfType().Single() is Oid keyUsage && - keyUsage.Value == "1.3.6.1.5.5.7.3.1"); - - // Subject alternative name - Assert.Contains( - httpsCertificate.Extensions.OfType(), - e => e.Critical == true && - e.Oid.Value == "2.5.29.17"); - - // ASP.NET HTTPS Development certificate extension - Assert.Contains( - httpsCertificate.Extensions.OfType(), - e => e.Critical == false && - e.Oid.Value == "1.3.6.1.4.1.311.84.1.1" && - Encoding.ASCII.GetString(e.RawData) == "ASP.NET Core HTTPS development certificate"); - - Assert.Equal(httpsCertificate.GetCertHashString(), exportedCertificate.GetCertHashString()); - - } - catch (Exception e) - { - Output.WriteLine(e.Message); - ListCertificates(Output); - throw; - } - } - - private void ListCertificates(ITestOutputHelper output) - { - using (var store = new X509Store(StoreName.My, StoreLocation.CurrentUser)) - { - store.Open(OpenFlags.ReadOnly); - var certificates = store.Certificates; - foreach (var certificate in certificates) - { - Output.WriteLine($"Certificate: '{Convert.ToBase64String(certificate.Export(X509ContentType.Cert))}'."); - certificate.Dispose(); - } - - store.Close(); - } - } - - [Fact] - public void EnsureCreateHttpsCertificate_DoesNotCreateACertificate_WhenThereIsAnExistingHttpsCertificates() - { - // Arrange - const string CertificateName = nameof(EnsureCreateHttpsCertificate_DoesNotCreateACertificate_WhenThereIsAnExistingHttpsCertificates) + ".pfx"; - var certificatePassword = Guid.NewGuid().ToString(); - - var manager = new CertificateManager(); - - manager.RemoveAllCertificates(CertificatePurpose.HTTPS, StoreName.My, StoreLocation.CurrentUser, TestCertificateSubject); - if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) - { - manager.RemoveAllCertificates(CertificatePurpose.HTTPS, StoreName.Root, StoreLocation.CurrentUser, TestCertificateSubject); - } - - DateTimeOffset now = DateTimeOffset.UtcNow; - now = new DateTimeOffset(now.Year, now.Month, now.Day, now.Hour, now.Minute, now.Second, 0, now.Offset); - manager.EnsureAspNetCoreHttpsDevelopmentCertificate(now, now.AddYears(1), path: null, trust: false, subject: TestCertificateSubject); - - var httpsCertificate = manager.ListCertificates(CertificatePurpose.HTTPS, StoreName.My, StoreLocation.CurrentUser, isValid: false).Single(c => c.Subject == TestCertificateSubject); - - // Act - var result = manager.EnsureAspNetCoreHttpsDevelopmentCertificate(now, now.AddYears(1), CertificateName, trust: false, includePrivateKey: true, password: certificatePassword, subject: TestCertificateSubject); - - // Assert - Assert.Equal(EnsureCertificateResult.ValidCertificatePresent, result); - Assert.True(File.Exists(CertificateName)); - - var exportedCertificate = new X509Certificate2(File.ReadAllBytes(CertificateName), certificatePassword); - Assert.NotNull(exportedCertificate); - Assert.True(exportedCertificate.HasPrivateKey); - - - Assert.Equal(httpsCertificate.GetCertHashString(), exportedCertificate.GetCertHashString()); - } - - [Fact(Skip = "Requires user interaction")] - public void EnsureAspNetCoreHttpsDevelopmentCertificate_ReturnsCorrectResult_WhenUserCancelsTrustStepOnWindows() - { - var manager = new CertificateManager(); - - manager.RemoveAllCertificates(CertificatePurpose.HTTPS, StoreName.My, StoreLocation.CurrentUser, TestCertificateSubject); - if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) - { - manager.RemoveAllCertificates(CertificatePurpose.HTTPS, StoreName.Root, StoreLocation.CurrentUser, TestCertificateSubject); - } - - DateTimeOffset now = DateTimeOffset.UtcNow; - now = new DateTimeOffset(now.Year, now.Month, now.Day, now.Hour, now.Minute, now.Second, 0, now.Offset); - var trustFailed = manager.EnsureAspNetCoreHttpsDevelopmentCertificate(now, now.AddYears(1), path: null, trust: true, subject: TestCertificateSubject); - - Assert.Equal(EnsureCertificateResult.UserCancelledTrustStep, trustFailed); - } - - [Fact(Skip = "Requires user interaction")] - public void EnsureAspNetCoreHttpsDevelopmentCertificate_CanRemoveCertificates() - { - var manager = new CertificateManager(); - - DateTimeOffset now = DateTimeOffset.UtcNow; - now = new DateTimeOffset(now.Year, now.Month, now.Day, now.Hour, now.Minute, now.Second, 0, now.Offset); - manager.EnsureAspNetCoreHttpsDevelopmentCertificate(now, now.AddYears(1), path: null, trust: true, subject: TestCertificateSubject); - - manager.CleanupHttpsCertificates(TestCertificateSubject); - - Assert.Empty(manager.ListCertificates(CertificatePurpose.HTTPS, StoreName.My, StoreLocation.CurrentUser, isValid: false).Where(c => c.Subject == TestCertificateSubject)); - if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) - { - Assert.Empty(manager.ListCertificates(CertificatePurpose.HTTPS, StoreName.Root, StoreLocation.CurrentUser, isValid: false).Where(c => c.Subject == TestCertificateSubject)); - } - } - - [Fact] - public void EnsureCreateIdentityTokenSigningCertificate_CreatesACertificate_WhenThereAreNoHttpsCertificates() - { - // Arrange - const string CertificateName = nameof(EnsureCreateIdentityTokenSigningCertificate_CreatesACertificate_WhenThereAreNoHttpsCertificates) + ".cer"; - var manager = new CertificateManager(); - - manager.RemoveAllCertificates(CertificatePurpose.Signing, StoreName.My, StoreLocation.CurrentUser, TestCertificateSubject); - if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) - { - manager.RemoveAllCertificates(CertificatePurpose.Signing, StoreName.Root, StoreLocation.CurrentUser, TestCertificateSubject); - } - - // Act - DateTimeOffset now = DateTimeOffset.UtcNow; - now = new DateTimeOffset(now.Year, now.Month, now.Day, now.Hour, now.Minute, now.Second, 0, now.Offset); - var result = manager.EnsureAspNetCoreApplicationTokensDevelopmentCertificate(now, now.AddYears(1), CertificateName, trust: false, subject: TestCertificateSubject); - - // Assert - Assert.Equal(EnsureCertificateResult.Succeeded, result); - Assert.True(File.Exists(CertificateName)); - - var exportedCertificate = new X509Certificate2(File.ReadAllBytes(CertificateName)); - Assert.NotNull(exportedCertificate); - Assert.False(exportedCertificate.HasPrivateKey); - - var identityCertificates = manager.ListCertificates(CertificatePurpose.Signing, StoreName.My, StoreLocation.CurrentUser, isValid: false); - var identityCertificate = Assert.Single(identityCertificates, i => i.Subject == TestCertificateSubject); - Assert.True(identityCertificate.HasPrivateKey); - Assert.Equal(TestCertificateSubject, identityCertificate.Subject); - Assert.Equal(TestCertificateSubject, identityCertificate.Issuer); - Assert.Equal("sha256RSA", identityCertificate.SignatureAlgorithm.FriendlyName); - Assert.Equal("1.2.840.113549.1.1.11", identityCertificate.SignatureAlgorithm.Value); - - Assert.Equal(now.LocalDateTime, identityCertificate.NotBefore); - Assert.Equal(now.AddYears(1).LocalDateTime, identityCertificate.NotAfter); - Assert.Contains( - identityCertificate.Extensions.OfType(), - e => e is X509BasicConstraintsExtension basicConstraints && - basicConstraints.Critical == true && - basicConstraints.CertificateAuthority == false && - basicConstraints.HasPathLengthConstraint == false && - basicConstraints.PathLengthConstraint == 0); - - Assert.Contains( - identityCertificate.Extensions.OfType(), - e => e is X509KeyUsageExtension keyUsage && - keyUsage.Critical == true && - keyUsage.KeyUsages == X509KeyUsageFlags.DigitalSignature); - - Assert.Contains( - identityCertificate.Extensions.OfType(), - e => e is X509EnhancedKeyUsageExtension enhancedKeyUsage && - enhancedKeyUsage.Critical == true && - enhancedKeyUsage.EnhancedKeyUsages.OfType().Single() is Oid keyUsage && - keyUsage.Value == "1.3.6.1.5.5.7.3.1"); - - // ASP.NET Core Identity Json Web Token signing development certificate - Assert.Contains( - identityCertificate.Extensions.OfType(), - e => e.Critical == false && - e.Oid.Value == "1.3.6.1.4.1.311.84.1.2" && - Encoding.ASCII.GetString(e.RawData) == "ASP.NET Core Identity Json Web Token signing development certificate"); - - Assert.Equal(identityCertificate.GetCertHashString(), exportedCertificate.GetCertHashString()); - } - - [Fact] - public void EnsureCreateIdentityTokenSigningCertificate_DoesNotCreateACertificate_WhenThereIsAnExistingHttpsCertificates() - { - // Arrange - const string CertificateName = nameof(EnsureCreateIdentityTokenSigningCertificate_DoesNotCreateACertificate_WhenThereIsAnExistingHttpsCertificates) + ".pfx"; - var certificatePassword = Guid.NewGuid().ToString(); - - var manager = new CertificateManager(); - - manager.RemoveAllCertificates(CertificatePurpose.Signing, StoreName.My, StoreLocation.CurrentUser, TestCertificateSubject); - if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) - { - manager.RemoveAllCertificates(CertificatePurpose.Signing, StoreName.Root, StoreLocation.CurrentUser, TestCertificateSubject); - } - - DateTimeOffset now = DateTimeOffset.UtcNow; - now = new DateTimeOffset(now.Year, now.Month, now.Day, now.Hour, now.Minute, now.Second, 0, now.Offset); - manager.EnsureAspNetCoreApplicationTokensDevelopmentCertificate(now, now.AddYears(1), path: null, trust: false, subject: TestCertificateSubject); - - var identityTokenSigningCertificates = manager.ListCertificates(CertificatePurpose.Signing, StoreName.My, StoreLocation.CurrentUser, isValid: false).Single(c => c.Subject == TestCertificateSubject); - - // Act - var result = manager.EnsureAspNetCoreApplicationTokensDevelopmentCertificate(now, now.AddYears(1), CertificateName, trust: false, includePrivateKey: true, password: certificatePassword, subject: TestCertificateSubject); - - // Assert - Assert.Equal(EnsureCertificateResult.ValidCertificatePresent, result); - Assert.True(File.Exists(CertificateName)); - - var exportedCertificate = new X509Certificate2(File.ReadAllBytes(CertificateName), certificatePassword); - Assert.NotNull(exportedCertificate); - Assert.True(exportedCertificate.HasPrivateKey); - - Assert.Equal(identityTokenSigningCertificates.GetCertHashString(), exportedCertificate.GetCertHashString()); - } - } -} - -#endif \ No newline at end of file From e8d84a19624358ac6917d6da8d00af8298a9a3bf Mon Sep 17 00:00:00 2001 From: Nate McMaster Date: Thu, 13 Dec 2018 10:43:02 -0800 Subject: [PATCH 0029/1101] Remove Microsoft.Extensions.CopyOnWriteDictionary.Sources This code will move to aspnet/AspNetCore \n\nCommit migrated from https://github.com/dotnet/extensions/commit/468940be7f7f7ed15881b828c3a60fe8ec6d5913 --- .../CopyOnWriteDictionary.cs | 155 ---------------- .../CopyOnWriteDictionaryHolder.cs | 166 ------------------ .../CopyOnWriteDictionaryHolderTest.cs | 91 ---------- .../Shared.Tests/CopyOnWriteDictionaryTest.cs | 109 ------------ 4 files changed, 521 deletions(-) delete mode 100644 src/Shared/CopyOnWriteDictionary/CopyOnWriteDictionary.cs delete mode 100644 src/Shared/CopyOnWriteDictionary/CopyOnWriteDictionaryHolder.cs delete mode 100644 src/Shared/test/Shared.Tests/CopyOnWriteDictionaryHolderTest.cs delete mode 100644 src/Shared/test/Shared.Tests/CopyOnWriteDictionaryTest.cs diff --git a/src/Shared/CopyOnWriteDictionary/CopyOnWriteDictionary.cs b/src/Shared/CopyOnWriteDictionary/CopyOnWriteDictionary.cs deleted file mode 100644 index 1408059ad9..0000000000 --- a/src/Shared/CopyOnWriteDictionary/CopyOnWriteDictionary.cs +++ /dev/null @@ -1,155 +0,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.Collections; -using System.Collections.Generic; - -namespace Microsoft.Extensions.Internal -{ - internal class CopyOnWriteDictionary : IDictionary - { - private readonly IDictionary _sourceDictionary; - private readonly IEqualityComparer _comparer; - private IDictionary _innerDictionary; - - public CopyOnWriteDictionary( - IDictionary sourceDictionary, - IEqualityComparer comparer) - { - if (sourceDictionary == null) - { - throw new ArgumentNullException(nameof(sourceDictionary)); - } - - if (comparer == null) - { - throw new ArgumentNullException(nameof(comparer)); - } - - _sourceDictionary = sourceDictionary; - _comparer = comparer; - } - - private IDictionary ReadDictionary - { - get - { - return _innerDictionary ?? _sourceDictionary; - } - } - - private IDictionary WriteDictionary - { - get - { - if (_innerDictionary == null) - { - _innerDictionary = new Dictionary(_sourceDictionary, - _comparer); - } - - return _innerDictionary; - } - } - - public virtual ICollection Keys - { - get - { - return ReadDictionary.Keys; - } - } - - public virtual ICollection Values - { - get - { - return ReadDictionary.Values; - } - } - - public virtual int Count - { - get - { - return ReadDictionary.Count; - } - } - - public virtual bool IsReadOnly - { - get - { - return false; - } - } - - public virtual TValue this[TKey key] - { - get - { - return ReadDictionary[key]; - } - set - { - WriteDictionary[key] = value; - } - } - - public virtual bool ContainsKey(TKey key) - { - return ReadDictionary.ContainsKey(key); - } - - public virtual void Add(TKey key, TValue value) - { - WriteDictionary.Add(key, value); - } - - public virtual bool Remove(TKey key) - { - return WriteDictionary.Remove(key); - } - - public virtual bool TryGetValue(TKey key, out TValue value) - { - return ReadDictionary.TryGetValue(key, out value); - } - - public virtual void Add(KeyValuePair item) - { - WriteDictionary.Add(item); - } - - public virtual void Clear() - { - WriteDictionary.Clear(); - } - - public virtual bool Contains(KeyValuePair item) - { - return ReadDictionary.Contains(item); - } - - public virtual void CopyTo(KeyValuePair[] array, int arrayIndex) - { - ReadDictionary.CopyTo(array, arrayIndex); - } - - public bool Remove(KeyValuePair item) - { - return WriteDictionary.Remove(item); - } - - public virtual IEnumerator> GetEnumerator() - { - return ReadDictionary.GetEnumerator(); - } - - IEnumerator IEnumerable.GetEnumerator() - { - return GetEnumerator(); - } - } -} \ No newline at end of file diff --git a/src/Shared/CopyOnWriteDictionary/CopyOnWriteDictionaryHolder.cs b/src/Shared/CopyOnWriteDictionary/CopyOnWriteDictionaryHolder.cs deleted file mode 100644 index 7cd935e940..0000000000 --- a/src/Shared/CopyOnWriteDictionary/CopyOnWriteDictionaryHolder.cs +++ /dev/null @@ -1,166 +0,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.Collections.Generic; - -namespace Microsoft.Extensions.Internal -{ - internal struct CopyOnWriteDictionaryHolder - { - private readonly Dictionary _source; - private Dictionary _copy; - - public CopyOnWriteDictionaryHolder(Dictionary source) - { - if (source == null) - { - throw new ArgumentNullException(nameof(source)); - } - - _source = source; - _copy = null; - } - - public CopyOnWriteDictionaryHolder(CopyOnWriteDictionaryHolder source) - { - _source = source._copy ?? source._source; - _copy = null; - } - - public bool HasBeenCopied => _copy != null; - - public Dictionary ReadDictionary - { - get - { - if (_copy != null) - { - return _copy; - } - else if (_source != null) - { - return _source; - } - else - { - // Default-Constructor case - _copy = new Dictionary(); - return _copy; - } - } - } - - public Dictionary WriteDictionary - { - get - { - if (_copy == null && _source == null) - { - // Default-Constructor case - _copy = new Dictionary(); - } - else if (_copy == null) - { - _copy = new Dictionary(_source, _source.Comparer); - } - - return _copy; - } - } - - public Dictionary.KeyCollection Keys - { - get - { - return ReadDictionary.Keys; - } - } - - public Dictionary.ValueCollection Values - { - get - { - return ReadDictionary.Values; - } - } - - public int Count - { - get - { - return ReadDictionary.Count; - } - } - - public bool IsReadOnly - { - get - { - return false; - } - } - - public TValue this[TKey key] - { - get - { - return ReadDictionary[key]; - } - set - { - WriteDictionary[key] = value; - } - } - - public bool ContainsKey(TKey key) - { - return ReadDictionary.ContainsKey(key); - } - - public void Add(TKey key, TValue value) - { - WriteDictionary.Add(key, value); - } - - public bool Remove(TKey key) - { - return WriteDictionary.Remove(key); - } - - public bool TryGetValue(TKey key, out TValue value) - { - return ReadDictionary.TryGetValue(key, out value); - } - - public void Add(KeyValuePair item) - { - ((ICollection>)WriteDictionary).Add(item); - } - - public void Clear() - { - WriteDictionary.Clear(); - } - - public bool Contains(KeyValuePair item) - { - return ((ICollection>)ReadDictionary).Contains(item); - } - - public void CopyTo(KeyValuePair[] array, int arrayIndex) - { - ((ICollection>)ReadDictionary).CopyTo(array, arrayIndex); - } - - public bool Remove(KeyValuePair item) - { - return ((ICollection>)WriteDictionary).Remove(item); - } - - public Dictionary.Enumerator GetEnumerator() - { - return ReadDictionary.GetEnumerator(); - } - } -} diff --git a/src/Shared/test/Shared.Tests/CopyOnWriteDictionaryHolderTest.cs b/src/Shared/test/Shared.Tests/CopyOnWriteDictionaryHolderTest.cs deleted file mode 100644 index 9a0951eb27..0000000000 --- a/src/Shared/test/Shared.Tests/CopyOnWriteDictionaryHolderTest.cs +++ /dev/null @@ -1,91 +0,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.Collections.Generic; -using System.Linq; -using Xunit; - -namespace Microsoft.Extensions.Internal -{ - public class CopyOnWriteDictionaryHolderTest - { - [Fact] - public void ReadOperation_DelegatesToSourceDictionary_IfNoMutationsArePerformed() - { - // Arrange - var source = new Dictionary(StringComparer.OrdinalIgnoreCase) - { - { "test-key", "test-value" }, - { "key2", "key2-value" } - }; - - var holder = new CopyOnWriteDictionaryHolder(source); - - // Act and Assert - Assert.Equal("key2-value", holder["key2"]); - Assert.Equal(2, holder.Count); - Assert.Equal(new string[] { "test-key", "key2" }, holder.Keys.ToArray()); - Assert.Equal(new object[] { "test-value", "key2-value" }, holder.Values.ToArray()); - Assert.True(holder.ContainsKey("test-key")); - - object value; - Assert.False(holder.TryGetValue("different-key", out value)); - - Assert.False(holder.HasBeenCopied); - Assert.Same(source, holder.ReadDictionary); - } - - [Fact] - public void ReadOperation_DoesNotDelegateToSourceDictionary_OnceAValueIsChanged() - { - // Arrange - var source = new Dictionary(StringComparer.OrdinalIgnoreCase) - { - { "key1", "value1" }, - { "key2", "value2" } - }; - - var holder = new CopyOnWriteDictionaryHolder(source); - - // Act - holder["key2"] = "value3"; - - // Assert - Assert.Equal("value2", source["key2"]); - Assert.Equal(2, holder.Count); - Assert.Equal("value1", holder["key1"]); - Assert.Equal("value3", holder["key2"]); - - Assert.True(holder.HasBeenCopied); - Assert.NotSame(source, holder.ReadDictionary); - } - - [Fact] - public void ReadOperation_DoesNotDelegateToSourceDictionary_OnceValueIsAdded() - { - // Arrange - var source = new Dictionary(StringComparer.OrdinalIgnoreCase) - { - { "key1", "value1" }, - { "key2", "value2" } - }; - - var holder = new CopyOnWriteDictionaryHolder(source); - - // Act - holder.Add("key3", "value3"); - holder.Remove("key1"); - - // Assert - Assert.Equal(2, source.Count); - Assert.Equal("value1", source["key1"]); - Assert.Equal(2, holder.Count); - Assert.Equal("value2", holder["KeY2"]); - Assert.Equal("value3", holder["key3"]); - - Assert.True(holder.HasBeenCopied); - Assert.NotSame(source, holder.ReadDictionary); - } - } -} diff --git a/src/Shared/test/Shared.Tests/CopyOnWriteDictionaryTest.cs b/src/Shared/test/Shared.Tests/CopyOnWriteDictionaryTest.cs deleted file mode 100644 index c1b54036d4..0000000000 --- a/src/Shared/test/Shared.Tests/CopyOnWriteDictionaryTest.cs +++ /dev/null @@ -1,109 +0,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.Collections.Generic; -using Moq; -using Xunit; - -namespace Microsoft.Extensions.Internal -{ - public class CopyOnWriteDictionaryTest - { - [Fact] - public void ReadOperation_DelegatesToSourceDictionary_IfNoMutationsArePerformed() - { - // Arrange - var values = new List(); - var enumerator = Mock.Of>>(); - var sourceDictionary = new Mock>(MockBehavior.Strict); - sourceDictionary - .SetupGet(d => d.Count) - .Returns(100) - .Verifiable(); - sourceDictionary - .SetupGet(d => d.Values) - .Returns(values) - .Verifiable(); - sourceDictionary - .Setup(d => d.ContainsKey("test-key")) - .Returns(value: true) - .Verifiable(); - sourceDictionary - .Setup(d => d.GetEnumerator()) - .Returns(enumerator) - .Verifiable(); - sourceDictionary - .Setup(d => d["key2"]) - .Returns("key2-value") - .Verifiable(); - object value; - sourceDictionary.Setup(d => d.TryGetValue("different-key", out value)) - .Returns(false) - .Verifiable(); - - var copyOnWriteDictionary = new CopyOnWriteDictionary(sourceDictionary.Object, - StringComparer.OrdinalIgnoreCase); - - // Act and Assert - Assert.Equal("key2-value", copyOnWriteDictionary["key2"]); - Assert.Equal(100, copyOnWriteDictionary.Count); - Assert.Same(values, copyOnWriteDictionary.Values); - Assert.True(copyOnWriteDictionary.ContainsKey("test-key")); - Assert.Same(enumerator, copyOnWriteDictionary.GetEnumerator()); - Assert.False(copyOnWriteDictionary.TryGetValue("different-key", out value)); - sourceDictionary.Verify(); - } - - [Fact] - public void ReadOperation_DoesNotDelegateToSourceDictionary_OnceAValueIsChanged() - { - // Arrange - var values = new List(); - var sourceDictionary = new Dictionary - { - { "key1", "value1" }, - { "key2", "value2" } - }; - var copyOnWriteDictionary = new CopyOnWriteDictionary( - sourceDictionary, - StringComparer.OrdinalIgnoreCase); - - // Act - copyOnWriteDictionary["key2"] = "value3"; - - // Assert - Assert.Equal("value2", sourceDictionary["key2"]); - Assert.Equal(2, copyOnWriteDictionary.Count); - Assert.Equal("value1", copyOnWriteDictionary["key1"]); - Assert.Equal("value3", copyOnWriteDictionary["key2"]); - } - - [Fact] - public void ReadOperation_DoesNotDelegateToSourceDictionary_OnceDictionaryIsModified() - { - // Arrange - var values = new List(); - var sourceDictionary = new Dictionary - { - { "key1", "value1" }, - { "key2", "value2" } - }; - var copyOnWriteDictionary = new CopyOnWriteDictionary( - sourceDictionary, - StringComparer.OrdinalIgnoreCase); - - // Act - copyOnWriteDictionary.Add("key3", "value3"); - copyOnWriteDictionary.Remove("key1"); - - - // Assert - Assert.Equal(2, sourceDictionary.Count); - Assert.Equal("value1", sourceDictionary["key1"]); - Assert.Equal(2, copyOnWriteDictionary.Count); - Assert.Equal("value2", copyOnWriteDictionary["KeY2"]); - Assert.Equal("value3", copyOnWriteDictionary["key3"]); - } - } -} From 2f36a1ecf9a0564f207c5c6b1a076e2d66367b39 Mon Sep 17 00:00:00 2001 From: Nate McMaster Date: Thu, 13 Dec 2018 11:01:34 -0800 Subject: [PATCH 0030/1101] Remove Microsoft.Extensions.ClosedGenericMatcher.Sources This code isn't used locally, so it will move to aspnet/AspNetCore \n\nCommit migrated from https://github.com/dotnet/extensions/commit/7305817fea3c4f20235c7bb377d8396928f8a9bb --- .../ClosedGenericMatcher.cs | 106 ------ .../Shared.Tests/ClosedGenericMatcherTest.cs | 360 ------------------ 2 files changed, 466 deletions(-) delete mode 100644 src/Shared/ClosedGenericMatcher/ClosedGenericMatcher.cs delete mode 100644 src/Shared/test/Shared.Tests/ClosedGenericMatcherTest.cs diff --git a/src/Shared/ClosedGenericMatcher/ClosedGenericMatcher.cs b/src/Shared/ClosedGenericMatcher/ClosedGenericMatcher.cs deleted file mode 100644 index f234c2edbc..0000000000 --- a/src/Shared/ClosedGenericMatcher/ClosedGenericMatcher.cs +++ /dev/null @@ -1,106 +0,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.Linq; -using System.Reflection; - -namespace Microsoft.Extensions.Internal -{ - /// - /// Helper related to generic interface definitions and implementing classes. - /// - internal static class ClosedGenericMatcher - { - /// - /// Determine whether is or implements a closed generic - /// created from . - /// - /// The of interest. - /// The open generic to match. Usually an interface. - /// - /// The closed generic created from that - /// is or implements. null if the two s have no such - /// relationship. - /// - /// - /// This method will return if is - /// typeof(KeyValuePair{,}), and is - /// typeof(KeyValuePair{string, object}). - /// - public static Type ExtractGenericInterface(Type queryType, Type interfaceType) - { - if (queryType == null) - { - throw new ArgumentNullException(nameof(queryType)); - } - - if (interfaceType == null) - { - throw new ArgumentNullException(nameof(interfaceType)); - } - - if (IsGenericInstantiation(queryType, interfaceType)) - { - // queryType matches (i.e. is a closed generic type created from) the open generic type. - return queryType; - } - - // Otherwise check all interfaces the type implements for a match. - // - If multiple different generic instantiations exists, we want the most derived one. - // - If that doesn't break the tie, then we sort alphabetically so that it's deterministic. - // - // We do this by looking at interfaces on the type, and recursing to the base type - // if we don't find any matches. - return GetGenericInstantiation(queryType, interfaceType); - } - - private static bool IsGenericInstantiation(Type candidate, Type interfaceType) - { - return - candidate.GetTypeInfo().IsGenericType && - candidate.GetGenericTypeDefinition() == interfaceType; - } - - private static Type GetGenericInstantiation(Type queryType, Type interfaceType) - { - Type bestMatch = null; - var interfaces = queryType.GetInterfaces(); - foreach (var @interface in interfaces) - { - if (IsGenericInstantiation(@interface, interfaceType)) - { - if (bestMatch == null) - { - bestMatch = @interface; - } - else if (StringComparer.Ordinal.Compare(@interface.FullName, bestMatch.FullName) < 0) - { - bestMatch = @interface; - } - else - { - // There are two matches at this level of the class hierarchy, but @interface is after - // bestMatch in the sort order. - } - } - } - - if (bestMatch != null) - { - return bestMatch; - } - - // BaseType will be null for object and interfaces, which means we've reached 'bottom'. - var baseType = queryType?.GetTypeInfo().BaseType; - if (baseType == null) - { - return null; - } - else - { - return GetGenericInstantiation(baseType, interfaceType); - } - } - } -} \ No newline at end of file diff --git a/src/Shared/test/Shared.Tests/ClosedGenericMatcherTest.cs b/src/Shared/test/Shared.Tests/ClosedGenericMatcherTest.cs deleted file mode 100644 index e71a792692..0000000000 --- a/src/Shared/test/Shared.Tests/ClosedGenericMatcherTest.cs +++ /dev/null @@ -1,360 +0,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.Collections; -using System.Collections.Generic; -using System.Collections.ObjectModel; -using Xunit; - -namespace Microsoft.Extensions.Internal -{ - public class ClosedGenericMatcherTest - { - // queryType, interfaceType, expectedResult - public static TheoryData ExtractGenericInterfaceDataSet - { - get - { - return new TheoryData - { - // Closed generic types that match given open generic type. - { - typeof(IEnumerable), - typeof(IEnumerable<>), - typeof(IEnumerable) - }, - { - typeof(IReadOnlyList), - typeof(IReadOnlyList<>), - typeof(IReadOnlyList) - }, - { - typeof(KeyValuePair), - typeof(KeyValuePair<,>), - typeof(KeyValuePair) - }, - // Closed generic interfaces that implement sub-interface of given open generic type. - { - typeof(ICollection), - typeof(IEnumerable<>), - typeof(IEnumerable) - }, - { - typeof(IReadOnlyList), - typeof(IEnumerable<>), - typeof(IEnumerable) - }, - { - typeof(IDictionary), - typeof(IEnumerable<>), - typeof(IEnumerable>) - }, - // Class that implements closed generic based on given open generic interface. - { - typeof(BaseClass), - typeof(IDictionary<,>), - typeof(IDictionary) - }, - { - typeof(BaseClass), - typeof(IEquatable<>), - typeof(IEquatable) - }, - { - typeof(BaseClass), - typeof(ICollection<>), - typeof(ICollection>) - }, - // Derived class that implements closed generic based on given open generic interface. - { - typeof(DerivedClass), - typeof(IDictionary<,>), - typeof(IDictionary) - }, - { - typeof(DerivedClass), - typeof(IEquatable<>), - typeof(IEquatable) - }, - { - typeof(DerivedClass), - typeof(ICollection<>), - typeof(ICollection>) - }, - // Derived class that also implements another interface. - { - typeof(DerivedClassWithComparable), - typeof(IDictionary<,>), - typeof(IDictionary) - }, - { - typeof(DerivedClassWithComparable), - typeof(IEquatable<>), - typeof(IEquatable) - }, - { - typeof(DerivedClassWithComparable), - typeof(ICollection<>), - typeof(ICollection>) - }, - { - typeof(DerivedClassWithComparable), - typeof(IComparable<>), - typeof(IComparable) - }, - // Derived class using system implementation. - { - typeof(DerivedClassFromSystemImplementation), - typeof(ICollection<>), - typeof(ICollection) - }, - { - typeof(DerivedClassFromSystemImplementation), - typeof(IReadOnlyList<>), - typeof(IReadOnlyList) - }, - { - typeof(DerivedClassFromSystemImplementation), - typeof(IEnumerable<>), - typeof(IEnumerable) - }, - // Not given an open generic type. - { - typeof(IEnumerable), - typeof(IEnumerable), - null - }, - { - typeof(IEnumerable), - typeof(IEnumerable), - null - }, - { - typeof(IReadOnlyList), - typeof(BaseClass), - null - }, - { - typeof(KeyValuePair<,>), - typeof(KeyValuePair), - null - }, - // Not a match. - { - typeof(IEnumerable), - typeof(IReadOnlyList<>), - null - }, - { - typeof(IList), - typeof(IReadOnlyList<>), - null - }, - { - typeof(IDictionary), - typeof(KeyValuePair<,>), - null - }, - }; - } - } - - [Theory] - [MemberData(nameof(ExtractGenericInterfaceDataSet))] - public void ExtractGenericInterface_ReturnsExpectedType( - Type queryType, - Type interfaceType, - Type expectedResult) - { - // Arrange & Act - var result = ClosedGenericMatcher.ExtractGenericInterface(queryType, interfaceType); - - // Assert - Assert.Equal(expectedResult, result); - } - - // IEnumerable is preferred because it is defined on the more-derived type. - [Fact] - public void ExtractGenericInterface_MultipleDefinitionsInherited() - { - // Arrange - var type = typeof(TwoIEnumerableImplementationsInherited); - - // Act - var result = ClosedGenericMatcher.ExtractGenericInterface(type, typeof(IEnumerable<>)); - - // Sort - Assert.Equal(typeof(IEnumerable), result); - } - - // IEnumerable is preferred because we sort by Ordinal on the full name. - [Fact] - public void ExtractGenericInterface_MultipleDefinitionsOnSameType() - { - // Arrange - var type = typeof(TwoIEnumerableImplementationsOnSameClass); - - // Act - var result = ClosedGenericMatcher.ExtractGenericInterface(type, typeof(IEnumerable<>)); - - // Sort - Assert.Equal(typeof(IEnumerable), result); - } - - private class TwoIEnumerableImplementationsOnSameClass : IEnumerable, IEnumerable - { - IEnumerator IEnumerable.GetEnumerator() - { - throw new NotImplementedException(); - } - - IEnumerator IEnumerable.GetEnumerator() - { - throw new NotImplementedException(); - } - - IEnumerator IEnumerable.GetEnumerator() - { - throw new NotImplementedException(); - } - } - - private class TwoIEnumerableImplementationsInherited : List, IEnumerable - { - IEnumerator IEnumerable.GetEnumerator() - { - throw new NotImplementedException(); - } - - IEnumerator IEnumerable.GetEnumerator() - { - throw new NotImplementedException(); - } - } - - private class BaseClass : IDictionary, IEquatable - { - object IDictionary.this[string key] - { - get - { - throw new NotImplementedException(); - } - - set - { - throw new NotImplementedException(); - } - } - - int ICollection>.Count - { - get - { - throw new NotImplementedException(); - } - } - - bool ICollection>.IsReadOnly - { - get - { - throw new NotImplementedException(); - } - } - - ICollection IDictionary.Keys - { - get - { - throw new NotImplementedException(); - } - } - - ICollection IDictionary.Values - { - get - { - throw new NotImplementedException(); - } - } - - public bool Equals(BaseClass other) - { - throw new NotImplementedException(); - } - - void ICollection>.Add(KeyValuePair item) - { - throw new NotImplementedException(); - } - - void IDictionary.Add(string key, object value) - { - throw new NotImplementedException(); - } - - void ICollection>.Clear() - { - throw new NotImplementedException(); - } - - bool ICollection>.Contains(KeyValuePair item) - { - throw new NotImplementedException(); - } - - bool IDictionary.ContainsKey(string key) - { - throw new NotImplementedException(); - } - - void ICollection>.CopyTo(KeyValuePair[] array, int arrayIndex) - { - throw new NotImplementedException(); - } - - IEnumerator IEnumerable.GetEnumerator() - { - throw new NotImplementedException(); - } - - IEnumerator> IEnumerable>.GetEnumerator() - { - throw new NotImplementedException(); - } - - bool ICollection>.Remove(KeyValuePair item) - { - throw new NotImplementedException(); - } - - bool IDictionary.Remove(string key) - { - throw new NotImplementedException(); - } - - bool IDictionary.TryGetValue(string key, out object value) - { - throw new NotImplementedException(); - } - } - - private class DerivedClass : BaseClass - { - } - - private class DerivedClassWithComparable : DerivedClass, IComparable - { - public int CompareTo(DerivedClassWithComparable other) - { - throw new NotImplementedException(); - } - } - - private class DerivedClassFromSystemImplementation : Collection - { - } - } -} \ No newline at end of file From bbf0ebdd91190dc412cf38a44d445035441ece9f Mon Sep 17 00:00:00 2001 From: Nate McMaster Date: Thu, 13 Dec 2018 11:04:12 -0800 Subject: [PATCH 0031/1101] Remove Microsoft.Extensions.ObjectMethodExecutor.Sources \n\nCommit migrated from https://github.com/dotnet/extensions/commit/71a672e28701ea6f3cc4034ba11891f7ce38189f --- .../ObjectMethodExecutor/AwaitableInfo.cs | 127 ---- .../CoercedAwaitableInfo.cs | 55 -- .../ObjectMethodExecutor.cs | 340 ---------- .../ObjectMethodExecutorAwaitable.cs | 114 ---- .../ObjectMethodExecutorFSharpSupport.cs | 151 ----- .../Shared.Tests/ObjectMethodExecutorTest.cs | 634 ------------------ 6 files changed, 1421 deletions(-) delete mode 100644 src/Shared/ObjectMethodExecutor/AwaitableInfo.cs delete mode 100644 src/Shared/ObjectMethodExecutor/CoercedAwaitableInfo.cs delete mode 100644 src/Shared/ObjectMethodExecutor/ObjectMethodExecutor.cs delete mode 100644 src/Shared/ObjectMethodExecutor/ObjectMethodExecutorAwaitable.cs delete mode 100644 src/Shared/ObjectMethodExecutor/ObjectMethodExecutorFSharpSupport.cs delete mode 100644 src/Shared/test/Shared.Tests/ObjectMethodExecutorTest.cs diff --git a/src/Shared/ObjectMethodExecutor/AwaitableInfo.cs b/src/Shared/ObjectMethodExecutor/AwaitableInfo.cs deleted file mode 100644 index 431b83a6e5..0000000000 --- a/src/Shared/ObjectMethodExecutor/AwaitableInfo.cs +++ /dev/null @@ -1,127 +0,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.Linq; -using System.Reflection; -using System.Runtime.CompilerServices; - -namespace Microsoft.Extensions.Internal -{ - internal struct AwaitableInfo - { - public Type AwaiterType { get; } - public PropertyInfo AwaiterIsCompletedProperty { get; } - public MethodInfo AwaiterGetResultMethod { get; } - public MethodInfo AwaiterOnCompletedMethod { get; } - public MethodInfo AwaiterUnsafeOnCompletedMethod { get; } - public Type ResultType { get; } - public MethodInfo GetAwaiterMethod { get; } - - public AwaitableInfo( - Type awaiterType, - PropertyInfo awaiterIsCompletedProperty, - MethodInfo awaiterGetResultMethod, - MethodInfo awaiterOnCompletedMethod, - MethodInfo awaiterUnsafeOnCompletedMethod, - Type resultType, - MethodInfo getAwaiterMethod) - { - AwaiterType = awaiterType; - AwaiterIsCompletedProperty = awaiterIsCompletedProperty; - AwaiterGetResultMethod = awaiterGetResultMethod; - AwaiterOnCompletedMethod = awaiterOnCompletedMethod; - AwaiterUnsafeOnCompletedMethod = awaiterUnsafeOnCompletedMethod; - ResultType = resultType; - GetAwaiterMethod = getAwaiterMethod; - } - - public static bool IsTypeAwaitable(Type type, out AwaitableInfo awaitableInfo) - { - // Based on Roslyn code: http://source.roslyn.io/#Microsoft.CodeAnalysis.Workspaces/Shared/Extensions/ISymbolExtensions.cs,db4d48ba694b9347 - - // Awaitable must have method matching "object GetAwaiter()" - var getAwaiterMethod = type.GetRuntimeMethods().FirstOrDefault(m => - m.Name.Equals("GetAwaiter", StringComparison.OrdinalIgnoreCase) - && m.GetParameters().Length == 0 - && m.ReturnType != null); - if (getAwaiterMethod == null) - { - awaitableInfo = default(AwaitableInfo); - return false; - } - - var awaiterType = getAwaiterMethod.ReturnType; - - // Awaiter must have property matching "bool IsCompleted { get; }" - var isCompletedProperty = awaiterType.GetRuntimeProperties().FirstOrDefault(p => - p.Name.Equals("IsCompleted", StringComparison.OrdinalIgnoreCase) - && p.PropertyType == typeof(bool) - && p.GetMethod != null); - if (isCompletedProperty == null) - { - awaitableInfo = default(AwaitableInfo); - return false; - } - - // Awaiter must implement INotifyCompletion - var awaiterInterfaces = awaiterType.GetInterfaces(); - var implementsINotifyCompletion = awaiterInterfaces.Any(t => t == typeof(INotifyCompletion)); - if (!implementsINotifyCompletion) - { - awaitableInfo = default(AwaitableInfo); - return false; - } - - // INotifyCompletion supplies a method matching "void OnCompleted(Action action)" - var iNotifyCompletionMap = awaiterType - .GetTypeInfo() - .GetRuntimeInterfaceMap(typeof(INotifyCompletion)); - var onCompletedMethod = iNotifyCompletionMap.InterfaceMethods.Single(m => - m.Name.Equals("OnCompleted", StringComparison.OrdinalIgnoreCase) - && m.ReturnType == typeof(void) - && m.GetParameters().Length == 1 - && m.GetParameters()[0].ParameterType == typeof(Action)); - - // Awaiter optionally implements ICriticalNotifyCompletion - var implementsICriticalNotifyCompletion = awaiterInterfaces.Any(t => t == typeof(ICriticalNotifyCompletion)); - MethodInfo unsafeOnCompletedMethod; - if (implementsICriticalNotifyCompletion) - { - // ICriticalNotifyCompletion supplies a method matching "void UnsafeOnCompleted(Action action)" - var iCriticalNotifyCompletionMap = awaiterType - .GetTypeInfo() - .GetRuntimeInterfaceMap(typeof(ICriticalNotifyCompletion)); - unsafeOnCompletedMethod = iCriticalNotifyCompletionMap.InterfaceMethods.Single(m => - m.Name.Equals("UnsafeOnCompleted", StringComparison.OrdinalIgnoreCase) - && m.ReturnType == typeof(void) - && m.GetParameters().Length == 1 - && m.GetParameters()[0].ParameterType == typeof(Action)); - } - else - { - unsafeOnCompletedMethod = null; - } - - // Awaiter must have method matching "void GetResult" or "T GetResult()" - var getResultMethod = awaiterType.GetRuntimeMethods().FirstOrDefault(m => - m.Name.Equals("GetResult") - && m.GetParameters().Length == 0); - if (getResultMethod == null) - { - awaitableInfo = default(AwaitableInfo); - return false; - } - - awaitableInfo = new AwaitableInfo( - awaiterType, - isCompletedProperty, - getResultMethod, - onCompletedMethod, - unsafeOnCompletedMethod, - getResultMethod.ReturnType, - getAwaiterMethod); - return true; - } - } -} diff --git a/src/Shared/ObjectMethodExecutor/CoercedAwaitableInfo.cs b/src/Shared/ObjectMethodExecutor/CoercedAwaitableInfo.cs deleted file mode 100644 index 4e48ef09a1..0000000000 --- a/src/Shared/ObjectMethodExecutor/CoercedAwaitableInfo.cs +++ /dev/null @@ -1,55 +0,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.Linq.Expressions; - -namespace Microsoft.Extensions.Internal -{ - internal struct CoercedAwaitableInfo - { - public AwaitableInfo AwaitableInfo { get; } - public Expression CoercerExpression { get; } - public Type CoercerResultType { get; } - public bool RequiresCoercion => CoercerExpression != null; - - public CoercedAwaitableInfo(AwaitableInfo awaitableInfo) - { - AwaitableInfo = awaitableInfo; - CoercerExpression = null; - CoercerResultType = null; - } - - public CoercedAwaitableInfo(Expression coercerExpression, Type coercerResultType, AwaitableInfo coercedAwaitableInfo) - { - CoercerExpression = coercerExpression; - CoercerResultType = coercerResultType; - AwaitableInfo = coercedAwaitableInfo; - } - - public static bool IsTypeAwaitable(Type type, out CoercedAwaitableInfo info) - { - if (AwaitableInfo.IsTypeAwaitable(type, out var directlyAwaitableInfo)) - { - info = new CoercedAwaitableInfo(directlyAwaitableInfo); - return true; - } - - // It's not directly awaitable, but maybe we can coerce it. - // Currently we support coercing FSharpAsync. - if (ObjectMethodExecutorFSharpSupport.TryBuildCoercerFromFSharpAsyncToAwaitable(type, - out var coercerExpression, - out var coercerResultType)) - { - if (AwaitableInfo.IsTypeAwaitable(coercerResultType, out var coercedAwaitableInfo)) - { - info = new CoercedAwaitableInfo(coercerExpression, coercerResultType, coercedAwaitableInfo); - return true; - } - } - - info = default(CoercedAwaitableInfo); - return false; - } - } -} diff --git a/src/Shared/ObjectMethodExecutor/ObjectMethodExecutor.cs b/src/Shared/ObjectMethodExecutor/ObjectMethodExecutor.cs deleted file mode 100644 index f8e5b70f0d..0000000000 --- a/src/Shared/ObjectMethodExecutor/ObjectMethodExecutor.cs +++ /dev/null @@ -1,340 +0,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.Collections.Generic; -using System.Linq.Expressions; -using System.Reflection; - -namespace Microsoft.Extensions.Internal -{ - internal class ObjectMethodExecutor - { - private readonly object[] _parameterDefaultValues; - private readonly MethodExecutorAsync _executorAsync; - private readonly MethodExecutor _executor; - - private static readonly ConstructorInfo _objectMethodExecutorAwaitableConstructor = - typeof(ObjectMethodExecutorAwaitable).GetConstructor(new[] { - typeof(object), // customAwaitable - typeof(Func), // getAwaiterMethod - typeof(Func), // isCompletedMethod - typeof(Func), // getResultMethod - typeof(Action), // onCompletedMethod - typeof(Action) // unsafeOnCompletedMethod - }); - - private ObjectMethodExecutor(MethodInfo methodInfo, TypeInfo targetTypeInfo, object[] parameterDefaultValues) - { - if (methodInfo == null) - { - throw new ArgumentNullException(nameof(methodInfo)); - } - - MethodInfo = methodInfo; - MethodParameters = methodInfo.GetParameters(); - TargetTypeInfo = targetTypeInfo; - MethodReturnType = methodInfo.ReturnType; - - var isAwaitable = CoercedAwaitableInfo.IsTypeAwaitable(MethodReturnType, out var coercedAwaitableInfo); - - IsMethodAsync = isAwaitable; - AsyncResultType = isAwaitable ? coercedAwaitableInfo.AwaitableInfo.ResultType : null; - - // Upstream code may prefer to use the sync-executor even for async methods, because if it knows - // that the result is a specific Task where T is known, then it can directly cast to that type - // and await it without the extra heap allocations involved in the _executorAsync code path. - _executor = GetExecutor(methodInfo, targetTypeInfo); - - if (IsMethodAsync) - { - _executorAsync = GetExecutorAsync(methodInfo, targetTypeInfo, coercedAwaitableInfo); - } - - _parameterDefaultValues = parameterDefaultValues; - } - - private delegate ObjectMethodExecutorAwaitable MethodExecutorAsync(object target, object[] parameters); - - private delegate object MethodExecutor(object target, object[] parameters); - - private delegate void VoidMethodExecutor(object target, object[] parameters); - - public MethodInfo MethodInfo { get; } - - public ParameterInfo[] MethodParameters { get; } - - public TypeInfo TargetTypeInfo { get; } - - public Type AsyncResultType { get; } - - // This field is made internal set because it is set in unit tests. - public Type MethodReturnType { get; internal set; } - - public bool IsMethodAsync { get; } - - public static ObjectMethodExecutor Create(MethodInfo methodInfo, TypeInfo targetTypeInfo) - { - return new ObjectMethodExecutor(methodInfo, targetTypeInfo, null); - } - - public static ObjectMethodExecutor Create(MethodInfo methodInfo, TypeInfo targetTypeInfo, object[] parameterDefaultValues) - { - if (parameterDefaultValues == null) - { - throw new ArgumentNullException(nameof(parameterDefaultValues)); - } - - return new ObjectMethodExecutor(methodInfo, targetTypeInfo, parameterDefaultValues); - } - - /// - /// Executes the configured method on . This can be used whether or not - /// the configured method is asynchronous. - /// - /// - /// Even if the target method is asynchronous, it's desirable to invoke it using Execute rather than - /// ExecuteAsync if you know at compile time what the return type is, because then you can directly - /// "await" that value (via a cast), and then the generated code will be able to reference the - /// resulting awaitable as a value-typed variable. If you use ExecuteAsync instead, the generated - /// code will have to treat the resulting awaitable as a boxed object, because it doesn't know at - /// compile time what type it would be. - /// - /// The object whose method is to be executed. - /// Parameters to pass to the method. - /// The method return value. - public object Execute(object target, object[] parameters) - { - return _executor(target, parameters); - } - - /// - /// Executes the configured method on . This can only be used if the configured - /// method is asynchronous. - /// - /// - /// If you don't know at compile time the type of the method's returned awaitable, you can use ExecuteAsync, - /// which supplies an awaitable-of-object. This always works, but can incur several extra heap allocations - /// as compared with using Execute and then using "await" on the result value typecasted to the known - /// awaitable type. The possible extra heap allocations are for: - /// - /// 1. The custom awaitable (though usually there's a heap allocation for this anyway, since normally - /// it's a reference type, and you normally create a new instance per call). - /// 2. The custom awaiter (whether or not it's a value type, since if it's not, you need a new instance - /// of it, and if it is, it will have to be boxed so the calling code can reference it as an object). - /// 3. The async result value, if it's a value type (it has to be boxed as an object, since the calling - /// code doesn't know what type it's going to be). - /// - /// The object whose method is to be executed. - /// Parameters to pass to the method. - /// An object that you can "await" to get the method return value. - public ObjectMethodExecutorAwaitable ExecuteAsync(object target, object[] parameters) - { - return _executorAsync(target, parameters); - } - - public object GetDefaultValueForParameter(int index) - { - if (_parameterDefaultValues == null) - { - throw new InvalidOperationException($"Cannot call {nameof(GetDefaultValueForParameter)}, because no parameter default values were supplied."); - } - - if (index < 0 || index > MethodParameters.Length - 1) - { - throw new ArgumentOutOfRangeException(nameof(index)); - } - - return _parameterDefaultValues[index]; - } - - private static MethodExecutor GetExecutor(MethodInfo methodInfo, TypeInfo targetTypeInfo) - { - // Parameters to executor - var targetParameter = Expression.Parameter(typeof(object), "target"); - var parametersParameter = Expression.Parameter(typeof(object[]), "parameters"); - - // Build parameter list - var parameters = new List(); - var paramInfos = methodInfo.GetParameters(); - for (int i = 0; i < paramInfos.Length; i++) - { - var paramInfo = paramInfos[i]; - var valueObj = Expression.ArrayIndex(parametersParameter, Expression.Constant(i)); - var valueCast = Expression.Convert(valueObj, paramInfo.ParameterType); - - // valueCast is "(Ti) parameters[i]" - parameters.Add(valueCast); - } - - // Call method - var instanceCast = Expression.Convert(targetParameter, targetTypeInfo.AsType()); - var methodCall = Expression.Call(instanceCast, methodInfo, parameters); - - // methodCall is "((Ttarget) target) method((T0) parameters[0], (T1) parameters[1], ...)" - // Create function - if (methodCall.Type == typeof(void)) - { - var lambda = Expression.Lambda(methodCall, targetParameter, parametersParameter); - var voidExecutor = lambda.Compile(); - return WrapVoidMethod(voidExecutor); - } - else - { - // must coerce methodCall to match ActionExecutor signature - var castMethodCall = Expression.Convert(methodCall, typeof(object)); - var lambda = Expression.Lambda(castMethodCall, targetParameter, parametersParameter); - return lambda.Compile(); - } - } - - private static MethodExecutor WrapVoidMethod(VoidMethodExecutor executor) - { - return delegate (object target, object[] parameters) - { - executor(target, parameters); - return null; - }; - } - - private static MethodExecutorAsync GetExecutorAsync( - MethodInfo methodInfo, - TypeInfo targetTypeInfo, - CoercedAwaitableInfo coercedAwaitableInfo) - { - // Parameters to executor - var targetParameter = Expression.Parameter(typeof(object), "target"); - var parametersParameter = Expression.Parameter(typeof(object[]), "parameters"); - - // Build parameter list - var parameters = new List(); - var paramInfos = methodInfo.GetParameters(); - for (int i = 0; i < paramInfos.Length; i++) - { - var paramInfo = paramInfos[i]; - var valueObj = Expression.ArrayIndex(parametersParameter, Expression.Constant(i)); - var valueCast = Expression.Convert(valueObj, paramInfo.ParameterType); - - // valueCast is "(Ti) parameters[i]" - parameters.Add(valueCast); - } - - // Call method - var instanceCast = Expression.Convert(targetParameter, targetTypeInfo.AsType()); - var methodCall = Expression.Call(instanceCast, methodInfo, parameters); - - // Using the method return value, construct an ObjectMethodExecutorAwaitable based on - // the info we have about its implementation of the awaitable pattern. Note that all - // the funcs/actions we construct here are precompiled, so that only one instance of - // each is preserved throughout the lifetime of the ObjectMethodExecutor. - - // var getAwaiterFunc = (object awaitable) => - // (object)((CustomAwaitableType)awaitable).GetAwaiter(); - var customAwaitableParam = Expression.Parameter(typeof(object), "awaitable"); - var awaitableInfo = coercedAwaitableInfo.AwaitableInfo; - var postCoercionMethodReturnType = coercedAwaitableInfo.CoercerResultType ?? methodInfo.ReturnType; - var getAwaiterFunc = Expression.Lambda>( - Expression.Convert( - Expression.Call( - Expression.Convert(customAwaitableParam, postCoercionMethodReturnType), - awaitableInfo.GetAwaiterMethod), - typeof(object)), - customAwaitableParam).Compile(); - - // var isCompletedFunc = (object awaiter) => - // ((CustomAwaiterType)awaiter).IsCompleted; - var isCompletedParam = Expression.Parameter(typeof(object), "awaiter"); - var isCompletedFunc = Expression.Lambda>( - Expression.MakeMemberAccess( - Expression.Convert(isCompletedParam, awaitableInfo.AwaiterType), - awaitableInfo.AwaiterIsCompletedProperty), - isCompletedParam).Compile(); - - var getResultParam = Expression.Parameter(typeof(object), "awaiter"); - Func getResultFunc; - if (awaitableInfo.ResultType == typeof(void)) - { - // var getResultFunc = (object awaiter) => - // { - // ((CustomAwaiterType)awaiter).GetResult(); // We need to invoke this to surface any exceptions - // return (object)null; - // }; - getResultFunc = Expression.Lambda>( - Expression.Block( - Expression.Call( - Expression.Convert(getResultParam, awaitableInfo.AwaiterType), - awaitableInfo.AwaiterGetResultMethod), - Expression.Constant(null) - ), - getResultParam).Compile(); - } - else - { - // var getResultFunc = (object awaiter) => - // (object)((CustomAwaiterType)awaiter).GetResult(); - getResultFunc = Expression.Lambda>( - Expression.Convert( - Expression.Call( - Expression.Convert(getResultParam, awaitableInfo.AwaiterType), - awaitableInfo.AwaiterGetResultMethod), - typeof(object)), - getResultParam).Compile(); - } - - // var onCompletedFunc = (object awaiter, Action continuation) => { - // ((CustomAwaiterType)awaiter).OnCompleted(continuation); - // }; - var onCompletedParam1 = Expression.Parameter(typeof(object), "awaiter"); - var onCompletedParam2 = Expression.Parameter(typeof(Action), "continuation"); - var onCompletedFunc = Expression.Lambda>( - Expression.Call( - Expression.Convert(onCompletedParam1, awaitableInfo.AwaiterType), - awaitableInfo.AwaiterOnCompletedMethod, - onCompletedParam2), - onCompletedParam1, - onCompletedParam2).Compile(); - - Action unsafeOnCompletedFunc = null; - if (awaitableInfo.AwaiterUnsafeOnCompletedMethod != null) - { - // var unsafeOnCompletedFunc = (object awaiter, Action continuation) => { - // ((CustomAwaiterType)awaiter).UnsafeOnCompleted(continuation); - // }; - var unsafeOnCompletedParam1 = Expression.Parameter(typeof(object), "awaiter"); - var unsafeOnCompletedParam2 = Expression.Parameter(typeof(Action), "continuation"); - unsafeOnCompletedFunc = Expression.Lambda>( - Expression.Call( - Expression.Convert(unsafeOnCompletedParam1, awaitableInfo.AwaiterType), - awaitableInfo.AwaiterUnsafeOnCompletedMethod, - unsafeOnCompletedParam2), - unsafeOnCompletedParam1, - unsafeOnCompletedParam2).Compile(); - } - - // If we need to pass the method call result through a coercer function to get an - // awaitable, then do so. - var coercedMethodCall = coercedAwaitableInfo.RequiresCoercion - ? Expression.Invoke(coercedAwaitableInfo.CoercerExpression, methodCall) - : (Expression)methodCall; - - // return new ObjectMethodExecutorAwaitable( - // (object)coercedMethodCall, - // getAwaiterFunc, - // isCompletedFunc, - // getResultFunc, - // onCompletedFunc, - // unsafeOnCompletedFunc); - var returnValueExpression = Expression.New( - _objectMethodExecutorAwaitableConstructor, - Expression.Convert(coercedMethodCall, typeof(object)), - Expression.Constant(getAwaiterFunc), - Expression.Constant(isCompletedFunc), - Expression.Constant(getResultFunc), - Expression.Constant(onCompletedFunc), - Expression.Constant(unsafeOnCompletedFunc, typeof(Action))); - - var lambda = Expression.Lambda(returnValueExpression, targetParameter, parametersParameter); - return lambda.Compile(); - } - } -} diff --git a/src/Shared/ObjectMethodExecutor/ObjectMethodExecutorAwaitable.cs b/src/Shared/ObjectMethodExecutor/ObjectMethodExecutorAwaitable.cs deleted file mode 100644 index 7509b86b2b..0000000000 --- a/src/Shared/ObjectMethodExecutor/ObjectMethodExecutorAwaitable.cs +++ /dev/null @@ -1,114 +0,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 Microsoft.Extensions.Internal -{ - /// - /// Provides a common awaitable structure that can - /// return, regardless of whether the underlying value is a System.Task, an FSharpAsync, or an - /// application-defined custom awaitable. - /// - internal struct ObjectMethodExecutorAwaitable - { - private readonly object _customAwaitable; - private readonly Func _getAwaiterMethod; - private readonly Func _isCompletedMethod; - private readonly Func _getResultMethod; - private readonly Action _onCompletedMethod; - private readonly Action _unsafeOnCompletedMethod; - - // Perf note: since we're requiring the customAwaitable to be supplied here as an object, - // this will trigger a further allocation if it was a value type (i.e., to box it). We can't - // fix this by making the customAwaitable type generic, because the calling code typically - // does not know the type of the awaitable/awaiter at compile-time anyway. - // - // However, we could fix it by not passing the customAwaitable here at all, and instead - // passing a func that maps directly from the target object (e.g., controller instance), - // target method (e.g., action method info), and params array to the custom awaiter in the - // GetAwaiter() method below. In effect, by delaying the actual method call until the - // upstream code calls GetAwaiter on this ObjectMethodExecutorAwaitable instance. - // This optimization is not currently implemented because: - // [1] It would make no difference when the awaitable was an object type, which is - // by far the most common scenario (e.g., System.Task). - // [2] It would be complex - we'd need some kind of object pool to track all the parameter - // arrays until we needed to use them in GetAwaiter(). - // We can reconsider this in the future if there's a need to optimize for ValueTask - // or other value-typed awaitables. - - public ObjectMethodExecutorAwaitable( - object customAwaitable, - Func getAwaiterMethod, - Func isCompletedMethod, - Func getResultMethod, - Action onCompletedMethod, - Action unsafeOnCompletedMethod) - { - _customAwaitable = customAwaitable; - _getAwaiterMethod = getAwaiterMethod; - _isCompletedMethod = isCompletedMethod; - _getResultMethod = getResultMethod; - _onCompletedMethod = onCompletedMethod; - _unsafeOnCompletedMethod = unsafeOnCompletedMethod; - } - - public Awaiter GetAwaiter() - { - var customAwaiter = _getAwaiterMethod(_customAwaitable); - return new Awaiter(customAwaiter, _isCompletedMethod, _getResultMethod, _onCompletedMethod, _unsafeOnCompletedMethod); - } - - public struct Awaiter : ICriticalNotifyCompletion - { - private readonly object _customAwaiter; - private readonly Func _isCompletedMethod; - private readonly Func _getResultMethod; - private readonly Action _onCompletedMethod; - private readonly Action _unsafeOnCompletedMethod; - - public Awaiter( - object customAwaiter, - Func isCompletedMethod, - Func getResultMethod, - Action onCompletedMethod, - Action unsafeOnCompletedMethod) - { - _customAwaiter = customAwaiter; - _isCompletedMethod = isCompletedMethod; - _getResultMethod = getResultMethod; - _onCompletedMethod = onCompletedMethod; - _unsafeOnCompletedMethod = unsafeOnCompletedMethod; - } - - public bool IsCompleted => _isCompletedMethod(_customAwaiter); - - public object GetResult() => _getResultMethod(_customAwaiter); - - public void OnCompleted(Action continuation) - { - _onCompletedMethod(_customAwaiter, continuation); - } - - public void UnsafeOnCompleted(Action continuation) - { - // If the underlying awaitable implements ICriticalNotifyCompletion, use its UnsafeOnCompleted. - // If not, fall back on using its OnCompleted. - // - // Why this is safe: - // - Implementing ICriticalNotifyCompletion is a way of saying the caller can choose whether it - // needs the execution context to be preserved (which it signals by calling OnCompleted), or - // that it doesn't (which it signals by calling UnsafeOnCompleted). Obviously it's faster *not* - // to preserve and restore the context, so we prefer that where possible. - // - If a caller doesn't need the execution context to be preserved and hence calls UnsafeOnCompleted, - // there's no harm in preserving it anyway - it's just a bit of wasted cost. That's what will happen - // if a caller sees that the proxy implements ICriticalNotifyCompletion but the proxy chooses to - // pass the call on to the underlying awaitable's OnCompleted method. - - var underlyingMethodToUse = _unsafeOnCompletedMethod ?? _onCompletedMethod; - underlyingMethodToUse(_customAwaiter, continuation); - } - } - } -} \ No newline at end of file diff --git a/src/Shared/ObjectMethodExecutor/ObjectMethodExecutorFSharpSupport.cs b/src/Shared/ObjectMethodExecutor/ObjectMethodExecutorFSharpSupport.cs deleted file mode 100644 index 2198c0ce45..0000000000 --- a/src/Shared/ObjectMethodExecutor/ObjectMethodExecutorFSharpSupport.cs +++ /dev/null @@ -1,151 +0,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.Linq; -using System.Linq.Expressions; -using System.Reflection; -using System.Threading; -using System.Threading.Tasks; - -namespace Microsoft.Extensions.Internal -{ - /// - /// Helper for detecting whether a given type is FSharpAsync`1, and if so, supplying - /// an for mapping instances of that type to a C# awaitable. - /// - /// - /// The main design goal here is to avoid taking a compile-time dependency on - /// FSharp.Core.dll, because non-F# applications wouldn't use it. So all the references - /// to FSharp types have to be constructed dynamically at runtime. - /// - internal static class ObjectMethodExecutorFSharpSupport - { - private static object _fsharpValuesCacheLock = new object(); - private static Assembly _fsharpCoreAssembly; - private static MethodInfo _fsharpAsyncStartAsTaskGenericMethod; - private static PropertyInfo _fsharpOptionOfTaskCreationOptionsNoneProperty; - private static PropertyInfo _fsharpOptionOfCancellationTokenNoneProperty; - - public static bool TryBuildCoercerFromFSharpAsyncToAwaitable( - Type possibleFSharpAsyncType, - out Expression coerceToAwaitableExpression, - out Type awaitableType) - { - var methodReturnGenericType = possibleFSharpAsyncType.IsGenericType - ? possibleFSharpAsyncType.GetGenericTypeDefinition() - : null; - - if (!IsFSharpAsyncOpenGenericType(methodReturnGenericType)) - { - coerceToAwaitableExpression = null; - awaitableType = null; - return false; - } - - var awaiterResultType = possibleFSharpAsyncType.GetGenericArguments().Single(); - awaitableType = typeof(Task<>).MakeGenericType(awaiterResultType); - - // coerceToAwaitableExpression = (object fsharpAsync) => - // { - // return (object)FSharpAsync.StartAsTask( - // (Microsoft.FSharp.Control.FSharpAsync)fsharpAsync, - // FSharpOption.None, - // FSharpOption.None); - // }; - var startAsTaskClosedMethod = _fsharpAsyncStartAsTaskGenericMethod - .MakeGenericMethod(awaiterResultType); - var coerceToAwaitableParam = Expression.Parameter(typeof(object)); - coerceToAwaitableExpression = Expression.Lambda( - Expression.Convert( - Expression.Call( - startAsTaskClosedMethod, - Expression.Convert(coerceToAwaitableParam, possibleFSharpAsyncType), - Expression.MakeMemberAccess(null, _fsharpOptionOfTaskCreationOptionsNoneProperty), - Expression.MakeMemberAccess(null, _fsharpOptionOfCancellationTokenNoneProperty)), - typeof(object)), - coerceToAwaitableParam); - - return true; - } - - private static bool IsFSharpAsyncOpenGenericType(Type possibleFSharpAsyncGenericType) - { - var typeFullName = possibleFSharpAsyncGenericType?.FullName; - if (!string.Equals(typeFullName, "Microsoft.FSharp.Control.FSharpAsync`1", StringComparison.Ordinal)) - { - return false; - } - - lock (_fsharpValuesCacheLock) - { - if (_fsharpCoreAssembly != null) - { - // Since we've already found the real FSharpAsync.Core assembly, we just have - // to check that the supplied FSharpAsync`1 type is the one from that assembly. - return possibleFSharpAsyncGenericType.Assembly == _fsharpCoreAssembly; - } - else - { - // We'll keep trying to find the FSharp types/values each time any type called - // FSharpAsync`1 is supplied. - return TryPopulateFSharpValueCaches(possibleFSharpAsyncGenericType); - } - } - } - - private static bool TryPopulateFSharpValueCaches(Type possibleFSharpAsyncGenericType) - { - var assembly = possibleFSharpAsyncGenericType.Assembly; - var fsharpOptionType = assembly.GetType("Microsoft.FSharp.Core.FSharpOption`1"); - var fsharpAsyncType = assembly.GetType("Microsoft.FSharp.Control.FSharpAsync"); - - if (fsharpOptionType == null || fsharpAsyncType == null) - { - return false; - } - - // Get a reference to FSharpOption.None - var fsharpOptionOfTaskCreationOptionsType = fsharpOptionType - .MakeGenericType(typeof(TaskCreationOptions)); - _fsharpOptionOfTaskCreationOptionsNoneProperty = fsharpOptionOfTaskCreationOptionsType - .GetTypeInfo() - .GetRuntimeProperty("None"); - - // Get a reference to FSharpOption.None - var fsharpOptionOfCancellationTokenType = fsharpOptionType - .MakeGenericType(typeof(CancellationToken)); - _fsharpOptionOfCancellationTokenNoneProperty = fsharpOptionOfCancellationTokenType - .GetTypeInfo() - .GetRuntimeProperty("None"); - - // Get a reference to FSharpAsync.StartAsTask<> - var fsharpAsyncMethods = fsharpAsyncType - .GetRuntimeMethods() - .Where(m => m.Name.Equals("StartAsTask", StringComparison.Ordinal)); - foreach (var candidateMethodInfo in fsharpAsyncMethods) - { - var parameters = candidateMethodInfo.GetParameters(); - if (parameters.Length == 3 - && TypesHaveSameIdentity(parameters[0].ParameterType, possibleFSharpAsyncGenericType) - && parameters[1].ParameterType == fsharpOptionOfTaskCreationOptionsType - && parameters[2].ParameterType == fsharpOptionOfCancellationTokenType) - { - // This really does look like the correct method (and hence assembly). - _fsharpAsyncStartAsTaskGenericMethod = candidateMethodInfo; - _fsharpCoreAssembly = assembly; - break; - } - } - - return _fsharpCoreAssembly != null; - } - - private static bool TypesHaveSameIdentity(Type type1, Type type2) - { - return type1.Assembly == type2.Assembly - && string.Equals(type1.Namespace, type2.Namespace, StringComparison.Ordinal) - && string.Equals(type1.Name, type2.Name, StringComparison.Ordinal); - } - } -} diff --git a/src/Shared/test/Shared.Tests/ObjectMethodExecutorTest.cs b/src/Shared/test/Shared.Tests/ObjectMethodExecutorTest.cs deleted file mode 100644 index 1c26ef1de1..0000000000 --- a/src/Shared/test/Shared.Tests/ObjectMethodExecutorTest.cs +++ /dev/null @@ -1,634 +0,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 Microsoft.FSharp.Control; -using Microsoft.FSharp.Core; -using System; -using System.Collections.Generic; -using System.Reflection; -using System.Runtime.CompilerServices; -using System.Threading; -using System.Threading.Tasks; -using Xunit; - -namespace Microsoft.Extensions.Internal -{ - public class ObjectMethodExecutorTest - { - private TestObject _targetObject = new TestObject(); - private TypeInfo targetTypeInfo = typeof(TestObject).GetTypeInfo(); - - [Fact] - public void ExecuteValueMethod() - { - var executor = GetExecutorForMethod("ValueMethod"); - var result = executor.Execute( - _targetObject, - new object[] { 10, 20 }); - Assert.False(executor.IsMethodAsync); - Assert.Equal(30, (int)result); - } - - [Fact] - public void ExecuteVoidValueMethod() - { - var executor = GetExecutorForMethod("VoidValueMethod"); - var result = executor.Execute( - _targetObject, - new object[] { 10 }); - Assert.False(executor.IsMethodAsync); - Assert.Null(result); - } - - [Fact] - public void ExecuteValueMethodWithReturnType() - { - var executor = GetExecutorForMethod("ValueMethodWithReturnType"); - var result = executor.Execute( - _targetObject, - new object[] { 10 }); - var resultObject = Assert.IsType(result); - Assert.False(executor.IsMethodAsync); - Assert.Equal("Hello", resultObject.value); - } - - [Fact] - public void ExecuteValueMethodUpdateValue() - { - var executor = GetExecutorForMethod("ValueMethodUpdateValue"); - var parameter = new TestObject(); - var result = executor.Execute( - _targetObject, - new object[] { parameter }); - var resultObject = Assert.IsType(result); - Assert.False(executor.IsMethodAsync); - Assert.Equal("HelloWorld", resultObject.value); - } - - [Fact] - public void ExecuteValueMethodWithReturnTypeThrowsException() - { - var executor = GetExecutorForMethod("ValueMethodWithReturnTypeThrowsException"); - var parameter = new TestObject(); - Assert.False(executor.IsMethodAsync); - Assert.Throws( - () => executor.Execute( - _targetObject, - new object[] { parameter })); - } - - [Fact] - public async Task ExecuteValueMethodAsync() - { - var executor = GetExecutorForMethod("ValueMethodAsync"); - var result = await executor.ExecuteAsync( - _targetObject, - new object[] { 10, 20 }); - Assert.True(executor.IsMethodAsync); - Assert.Equal(30, (int)result); - } - - [Fact] - public async Task ExecuteValueMethodWithReturnTypeAsync() - { - var executor = GetExecutorForMethod("ValueMethodWithReturnTypeAsync"); - var result = await executor.ExecuteAsync( - _targetObject, - new object[] { 10 }); - var resultObject = Assert.IsType(result); - Assert.True(executor.IsMethodAsync); - Assert.Equal("Hello", resultObject.value); - } - - [Fact] - public async Task ExecuteValueMethodUpdateValueAsync() - { - var executor = GetExecutorForMethod("ValueMethodUpdateValueAsync"); - var parameter = new TestObject(); - var result = await executor.ExecuteAsync( - _targetObject, - new object[] { parameter }); - var resultObject = Assert.IsType(result); - Assert.True(executor.IsMethodAsync); - Assert.Equal("HelloWorld", resultObject.value); - } - - [Fact] - public async Task ExecuteValueMethodWithReturnTypeThrowsExceptionAsync() - { - var executor = GetExecutorForMethod("ValueMethodWithReturnTypeThrowsExceptionAsync"); - var parameter = new TestObject(); - Assert.True(executor.IsMethodAsync); - await Assert.ThrowsAsync( - async () => await executor.ExecuteAsync( - _targetObject, - new object[] { parameter })); - } - - [Fact] - public async Task ExecuteValueMethodWithReturnVoidThrowsExceptionAsync() - { - var executor = GetExecutorForMethod("ValueMethodWithReturnVoidThrowsExceptionAsync"); - var parameter = new TestObject(); - Assert.True(executor.IsMethodAsync); - await Assert.ThrowsAsync( - async () => await executor.ExecuteAsync( - _targetObject, - new object[] { parameter })); - } - - [Fact] - public void GetDefaultValueForParameters_ReturnsSuppliedValues() - { - var suppliedDefaultValues = new object[] { 123, "test value" }; - var executor = GetExecutorForMethod("MethodWithMultipleParameters", suppliedDefaultValues); - Assert.Equal(suppliedDefaultValues[0], executor.GetDefaultValueForParameter(0)); - Assert.Equal(suppliedDefaultValues[1], executor.GetDefaultValueForParameter(1)); - Assert.Throws(() => executor.GetDefaultValueForParameter(2)); - } - - [Fact] - public void GetDefaultValueForParameters_ThrowsIfNoneWereSupplied() - { - var executor = GetExecutorForMethod("MethodWithMultipleParameters"); - Assert.Throws(() => executor.GetDefaultValueForParameter(0)); - } - - [Fact] - public async void TargetMethodReturningCustomAwaitableOfReferenceType_CanInvokeViaExecute() - { - // Arrange - var executor = GetExecutorForMethod("CustomAwaitableOfReferenceTypeAsync"); - - // Act - var result = await (TestAwaitable)executor.Execute(_targetObject, new object[] { "Hello", 123 }); - - // Assert - Assert.True(executor.IsMethodAsync); - Assert.Same(typeof(TestObject), executor.AsyncResultType); - Assert.NotNull(result); - Assert.Equal("Hello 123", result.value); - } - - [Fact] - public async void TargetMethodReturningCustomAwaitableOfValueType_CanInvokeViaExecute() - { - // Arrange - var executor = GetExecutorForMethod("CustomAwaitableOfValueTypeAsync"); - - // Act - var result = await (TestAwaitable)executor.Execute(_targetObject, new object[] { 123, 456 }); - - // Assert - Assert.True(executor.IsMethodAsync); - Assert.Same(typeof(int), executor.AsyncResultType); - Assert.Equal(579, result); - } - - [Fact] - public async void TargetMethodReturningCustomAwaitableOfReferenceType_CanInvokeViaExecuteAsync() - { - // Arrange - var executor = GetExecutorForMethod("CustomAwaitableOfReferenceTypeAsync"); - - // Act - var result = await executor.ExecuteAsync(_targetObject, new object[] { "Hello", 123 }); - - // Assert - Assert.True(executor.IsMethodAsync); - Assert.Same(typeof(TestObject), executor.AsyncResultType); - Assert.NotNull(result); - Assert.IsType(result); - Assert.Equal("Hello 123", ((TestObject)result).value); - } - - [Fact] - public async void TargetMethodReturningCustomAwaitableOfValueType_CanInvokeViaExecuteAsync() - { - // Arrange - var executor = GetExecutorForMethod("CustomAwaitableOfValueTypeAsync"); - - // Act - var result = await executor.ExecuteAsync(_targetObject, new object[] { 123, 456 }); - - // Assert - Assert.True(executor.IsMethodAsync); - Assert.Same(typeof(int), executor.AsyncResultType); - Assert.NotNull(result); - Assert.IsType(result); - Assert.Equal(579, (int)result); - } - - [Fact] - public async void TargetMethodReturningAwaitableOfVoidType_CanInvokeViaExecuteAsync() - { - // Arrange - var executor = GetExecutorForMethod("VoidValueMethodAsync"); - - // Act - var result = await executor.ExecuteAsync(_targetObject, new object[] { 123 }); - - // Assert - Assert.True(executor.IsMethodAsync); - Assert.Same(typeof(void), executor.AsyncResultType); - Assert.Null(result); - } - - [Fact] - public async void TargetMethodReturningAwaitableWithICriticalNotifyCompletion_UsesUnsafeOnCompleted() - { - // Arrange - var executor = GetExecutorForMethod("CustomAwaitableWithICriticalNotifyCompletion"); - - // Act - var result = await executor.ExecuteAsync(_targetObject, new object[0]); - - // Assert - Assert.True(executor.IsMethodAsync); - Assert.Same(typeof(string), executor.AsyncResultType); - Assert.Equal("Used UnsafeOnCompleted", (string)result); - } - - [Fact] - public async void TargetMethodReturningAwaitableWithoutICriticalNotifyCompletion_UsesOnCompleted() - { - // Arrange - var executor = GetExecutorForMethod("CustomAwaitableWithoutICriticalNotifyCompletion"); - - // Act - var result = await executor.ExecuteAsync(_targetObject, new object[0]); - - // Assert - Assert.True(executor.IsMethodAsync); - Assert.Same(typeof(string), executor.AsyncResultType); - Assert.Equal("Used OnCompleted", (string)result); - } - - [Fact] - public async void TargetMethodReturningValueTaskOfValueType_CanBeInvokedViaExecute() - { - // Arrange - var executor = GetExecutorForMethod("ValueTaskOfValueType"); - - // Act - var result = await (ValueTask)executor.Execute(_targetObject, new object[] { 123 }); - - // Assert - Assert.True(executor.IsMethodAsync); - Assert.Same(typeof(int), executor.AsyncResultType); - Assert.Equal(123, result); - } - - [Fact] - public async void TargetMethodReturningValueTaskOfReferenceType_CanBeInvokedViaExecute() - { - // Arrange - var executor = GetExecutorForMethod("ValueTaskOfReferenceType"); - - // Act - var result = await (ValueTask)executor.Execute(_targetObject, new object[] { "test result" }); - - // Assert - Assert.True(executor.IsMethodAsync); - Assert.Same(typeof(string), executor.AsyncResultType); - Assert.Equal("test result", result); - } - - [Fact] - public async void TargetMethodReturningValueTaskOfValueType_CanBeInvokedViaExecuteAsync() - { - // Arrange - var executor = GetExecutorForMethod("ValueTaskOfValueType"); - - // Act - var result = await executor.ExecuteAsync(_targetObject, new object[] { 123 }); - - // Assert - Assert.True(executor.IsMethodAsync); - Assert.Same(typeof(int), executor.AsyncResultType); - Assert.NotNull(result); - Assert.Equal(123, (int)result); - } - - [Fact] - public async void TargetMethodReturningValueTaskOfReferenceType_CanBeInvokedViaExecuteAsync() - { - // Arrange - var executor = GetExecutorForMethod("ValueTaskOfReferenceType"); - - // Act - var result = await executor.ExecuteAsync(_targetObject, new object[] { "test result" }); - - // Assert - Assert.True(executor.IsMethodAsync); - Assert.Same(typeof(string), executor.AsyncResultType); - Assert.Equal("test result", result); - } - - [Fact] - public async void TargetMethodReturningFSharpAsync_CanBeInvokedViaExecute() - { - // Arrange - var executor = GetExecutorForMethod("FSharpAsyncMethod"); - - // Act - var fsharpAsync = (FSharpAsync)executor.Execute(_targetObject, new object[] { "test result" }); - var result = await FSharpAsync.StartAsTask(fsharpAsync, - FSharpOption.None, - FSharpOption.None); - - // Assert - Assert.True(executor.IsMethodAsync); - Assert.Same(typeof(string), executor.AsyncResultType); - Assert.Equal("test result", result); - } - - [Fact] - public async void TargetMethodReturningFailingFSharpAsync_CanBeInvokedViaExecute() - { - // Arrange - var executor = GetExecutorForMethod("FSharpAsyncFailureMethod"); - - // Act - var fsharpAsync = (FSharpAsync)executor.Execute(_targetObject, new object[] { "test result" }); - var resultTask = FSharpAsync.StartAsTask(fsharpAsync, - FSharpOption.None, - FSharpOption.None); - - // Assert - Assert.True(executor.IsMethodAsync); - Assert.Same(typeof(string), executor.AsyncResultType); - - var exception = await Assert.ThrowsAsync(async () => await resultTask); - Assert.IsType(exception.InnerException); - Assert.Equal("Test exception", exception.InnerException.Message); - } - - [Fact] - public async void TargetMethodReturningFSharpAsync_CanBeInvokedViaExecuteAsync() - { - // Arrange - var executor = GetExecutorForMethod("FSharpAsyncMethod"); - - // Act - var result = await executor.ExecuteAsync(_targetObject, new object[] { "test result" }); - - // Assert - Assert.True(executor.IsMethodAsync); - Assert.Same(typeof(string), executor.AsyncResultType); - Assert.Equal("test result", result); - } - - [Fact] - public async void TargetMethodReturningFailingFSharpAsync_CanBeInvokedViaExecuteAsync() - { - // Arrange - var executor = GetExecutorForMethod("FSharpAsyncFailureMethod"); - - // Act - var resultTask = executor.ExecuteAsync(_targetObject, new object[] { "test result" }); - - // Assert - Assert.True(executor.IsMethodAsync); - Assert.Same(typeof(string), executor.AsyncResultType); - - var exception = await Assert.ThrowsAsync(async () => await resultTask); - Assert.IsType(exception.InnerException); - Assert.Equal("Test exception", exception.InnerException.Message); - } - - private ObjectMethodExecutor GetExecutorForMethod(string methodName) - { - var method = typeof(TestObject).GetMethod(methodName); - return ObjectMethodExecutor.Create(method, targetTypeInfo); - } - - private ObjectMethodExecutor GetExecutorForMethod(string methodName, object[] parameterDefaultValues) - { - var method = typeof(TestObject).GetMethod(methodName); - return ObjectMethodExecutor.Create(method, targetTypeInfo, parameterDefaultValues); - } - - public class TestObject - { - public string value; - public int ValueMethod(int i, int j) - { - return i + j; - } - - public void VoidValueMethod(int i) - { - - } - - public TestObject ValueMethodWithReturnType(int i) - { - return new TestObject() { value = "Hello" }; ; - } - - public TestObject ValueMethodWithReturnTypeThrowsException(TestObject i) - { - throw new NotImplementedException("Not Implemented Exception"); - } - - public TestObject ValueMethodUpdateValue(TestObject parameter) - { - parameter.value = "HelloWorld"; - return parameter; - } - - public Task ValueMethodAsync(int i, int j) - { - return Task.FromResult(i + j); - } - - public async Task VoidValueMethodAsync(int i) - { - await ValueMethodAsync(3, 4); - } - public Task ValueMethodWithReturnTypeAsync(int i) - { - return Task.FromResult(new TestObject() { value = "Hello" }); - } - - public async Task ValueMethodWithReturnVoidThrowsExceptionAsync(TestObject i) - { - await Task.CompletedTask; - throw new NotImplementedException("Not Implemented Exception"); - } - - public async Task ValueMethodWithReturnTypeThrowsExceptionAsync(TestObject i) - { - await Task.CompletedTask; - throw new NotImplementedException("Not Implemented Exception"); - } - - public Task ValueMethodUpdateValueAsync(TestObject parameter) - { - parameter.value = "HelloWorld"; - return Task.FromResult(parameter); - } - - public TestAwaitable CustomAwaitableOfReferenceTypeAsync( - string input1, - int input2) - { - return new TestAwaitable(new TestObject - { - value = $"{input1} {input2}" - }); - } - - public TestAwaitable CustomAwaitableOfValueTypeAsync( - int input1, - int input2) - { - return new TestAwaitable(input1 + input2); - } - - public TestAwaitableWithICriticalNotifyCompletion CustomAwaitableWithICriticalNotifyCompletion() - { - return new TestAwaitableWithICriticalNotifyCompletion(); - } - - public TestAwaitableWithoutICriticalNotifyCompletion CustomAwaitableWithoutICriticalNotifyCompletion() - { - return new TestAwaitableWithoutICriticalNotifyCompletion(); - } - - public ValueTask ValueTaskOfValueType(int result) - { - return new ValueTask(result); - } - - public ValueTask ValueTaskOfReferenceType(string result) - { - return new ValueTask(result); - } - - public void MethodWithMultipleParameters(int valueTypeParam, string referenceTypeParam) - { - } - - public FSharpAsync FSharpAsyncMethod(string parameter) - { - return FSharpAsync.AwaitTask(Task.FromResult(parameter)); - } - - public FSharpAsync FSharpAsyncFailureMethod(string parameter) - { - return FSharpAsync.AwaitTask( - Task.FromException(new InvalidOperationException("Test exception"))); - } - } - - public class TestAwaitable - { - private T _result; - private bool _isCompleted; - private List _onCompletedCallbacks = new List(); - - public TestAwaitable(T result) - { - _result = result; - - // Simulate a brief delay before completion - ThreadPool.QueueUserWorkItem(_ => - { - Thread.Sleep(100); - SetCompleted(); - }); - } - - private void SetCompleted() - { - _isCompleted = true; - - foreach (var callback in _onCompletedCallbacks) - { - callback(); - } - } - - public TestAwaiter GetAwaiter() - { - return new TestAwaiter(this); - } - - public struct TestAwaiter : INotifyCompletion - { - private TestAwaitable _owner; - - public TestAwaiter(TestAwaitable owner) : this() - { - _owner = owner; - } - - public bool IsCompleted => _owner._isCompleted; - - public void OnCompleted(Action continuation) - { - if (_owner._isCompleted) - { - continuation(); - } - else - { - _owner._onCompletedCallbacks.Add(continuation); - } - } - - public T GetResult() - { - return _owner._result; - } - } - } - - public class TestAwaitableWithICriticalNotifyCompletion - { - public TestAwaiterWithICriticalNotifyCompletion GetAwaiter() - => new TestAwaiterWithICriticalNotifyCompletion(); - } - - public class TestAwaitableWithoutICriticalNotifyCompletion - { - public TestAwaiterWithoutICriticalNotifyCompletion GetAwaiter() - => new TestAwaiterWithoutICriticalNotifyCompletion(); - } - - public class TestAwaiterWithICriticalNotifyCompletion - : CompletionTrackingAwaiterBase, ICriticalNotifyCompletion - { - } - - public class TestAwaiterWithoutICriticalNotifyCompletion - : CompletionTrackingAwaiterBase, INotifyCompletion - { - } - - public class CompletionTrackingAwaiterBase - { - private string _result; - - public bool IsCompleted { get; private set; } - - public string GetResult() => _result; - - public void OnCompleted(Action continuation) - { - _result = "Used OnCompleted"; - IsCompleted = true; - continuation(); - } - - public void UnsafeOnCompleted(Action continuation) - { - _result = "Used UnsafeOnCompleted"; - IsCompleted = true; - continuation(); - } - } - } -} From 921060b2cac2be3c71b1989676e6c37f80831a9c Mon Sep 17 00:00:00 2001 From: Nate McMaster Date: Thu, 13 Dec 2018 11:15:55 -0800 Subject: [PATCH 0032/1101] Remove Microsoft.Extensions.PropertyActivator.Sources and Microsoft.Extensions.PropertyHelper.Sources \n\nCommit migrated from https://github.com/dotnet/extensions/commit/6c11818fe9883e6ef5bb0dd85ff460b9de92737a --- .../PropertyActivator/PropertyActivator.cs | 110 --- src/Shared/PropertyHelper/PropertyHelper.cs | 526 ----------- .../Microsoft.AspNetCore.Shared.Tests.csproj | 1 - .../Shared.Tests/PropertyActivatorTest.cs | 187 ---- .../test/Shared.Tests/PropertyHelperTest.cs | 831 ------------------ 5 files changed, 1655 deletions(-) delete mode 100644 src/Shared/PropertyActivator/PropertyActivator.cs delete mode 100644 src/Shared/PropertyHelper/PropertyHelper.cs delete mode 100644 src/Shared/test/Shared.Tests/PropertyActivatorTest.cs delete mode 100644 src/Shared/test/Shared.Tests/PropertyHelperTest.cs diff --git a/src/Shared/PropertyActivator/PropertyActivator.cs b/src/Shared/PropertyActivator/PropertyActivator.cs deleted file mode 100644 index 925f6a76ae..0000000000 --- a/src/Shared/PropertyActivator/PropertyActivator.cs +++ /dev/null @@ -1,110 +0,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.Linq; -using System.Reflection; - -namespace Microsoft.Extensions.Internal -{ - internal class PropertyActivator - { - private readonly Func _valueAccessor; - private readonly Action _fastPropertySetter; - - public PropertyActivator( - PropertyInfo propertyInfo, - Func valueAccessor) - { - if (propertyInfo == null) - { - throw new ArgumentNullException(nameof(propertyInfo)); - } - - if (valueAccessor == null) - { - throw new ArgumentNullException(nameof(valueAccessor)); - } - - PropertyInfo = propertyInfo; - _valueAccessor = valueAccessor; - _fastPropertySetter = PropertyHelper.MakeFastPropertySetter(propertyInfo); - } - - public PropertyInfo PropertyInfo { get; private set; } - - public object Activate(object instance, TContext context) - { - if (instance == null) - { - throw new ArgumentNullException(nameof(instance)); - } - - var value = _valueAccessor(context); - _fastPropertySetter(instance, value); - return value; - } - - public static PropertyActivator[] GetPropertiesToActivate( - Type type, - Type activateAttributeType, - Func> createActivateInfo) - { - if (type == null) - { - throw new ArgumentNullException(nameof(type)); - } - - if (activateAttributeType == null) - { - throw new ArgumentNullException(nameof(activateAttributeType)); - } - - if (createActivateInfo == null) - { - throw new ArgumentNullException(nameof(createActivateInfo)); - } - - return GetPropertiesToActivate(type, activateAttributeType, createActivateInfo, includeNonPublic: false); - } - - public static PropertyActivator[] GetPropertiesToActivate( - Type type, - Type activateAttributeType, - Func> createActivateInfo, - bool includeNonPublic) - { - if (type == null) - { - throw new ArgumentNullException(nameof(type)); - } - - if (activateAttributeType == null) - { - throw new ArgumentNullException(nameof(activateAttributeType)); - } - - if (createActivateInfo == null) - { - throw new ArgumentNullException(nameof(createActivateInfo)); - } - - var properties = type.GetRuntimeProperties() - .Where((property) => - { - return - property.IsDefined(activateAttributeType) && - property.GetIndexParameters().Length == 0 && - property.SetMethod != null && - !property.SetMethod.IsStatic; - }); - - if (!includeNonPublic) - { - properties = properties.Where(property => property.SetMethod.IsPublic); - } - - return properties.Select(createActivateInfo).ToArray(); - } - } -} \ No newline at end of file diff --git a/src/Shared/PropertyHelper/PropertyHelper.cs b/src/Shared/PropertyHelper/PropertyHelper.cs deleted file mode 100644 index 27ba5661a4..0000000000 --- a/src/Shared/PropertyHelper/PropertyHelper.cs +++ /dev/null @@ -1,526 +0,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.Collections.Concurrent; -using System.Collections.Generic; -using System.Diagnostics; -using System.Linq; -using System.Reflection; - -namespace Microsoft.Extensions.Internal -{ - internal class PropertyHelper - { - // Delegate type for a by-ref property getter - private delegate TValue ByRefFunc(ref TDeclaringType arg); - - private static readonly MethodInfo CallPropertyGetterOpenGenericMethod = - typeof(PropertyHelper).GetTypeInfo().GetDeclaredMethod(nameof(CallPropertyGetter)); - - private static readonly MethodInfo CallPropertyGetterByReferenceOpenGenericMethod = - typeof(PropertyHelper).GetTypeInfo().GetDeclaredMethod(nameof(CallPropertyGetterByReference)); - - private static readonly MethodInfo CallNullSafePropertyGetterOpenGenericMethod = - typeof(PropertyHelper).GetTypeInfo().GetDeclaredMethod(nameof(CallNullSafePropertyGetter)); - - private static readonly MethodInfo CallNullSafePropertyGetterByReferenceOpenGenericMethod = - typeof(PropertyHelper).GetTypeInfo().GetDeclaredMethod(nameof(CallNullSafePropertyGetterByReference)); - - private static readonly MethodInfo CallPropertySetterOpenGenericMethod = - typeof(PropertyHelper).GetTypeInfo().GetDeclaredMethod(nameof(CallPropertySetter)); - - // Using an array rather than IEnumerable, as target will be called on the hot path numerous times. - private static readonly ConcurrentDictionary PropertiesCache = - new ConcurrentDictionary(); - - private static readonly ConcurrentDictionary VisiblePropertiesCache = - new ConcurrentDictionary(); - - private Action _valueSetter; - private Func _valueGetter; - - /// - /// Initializes a fast . - /// This constructor does not cache the helper. For caching, use . - /// - public PropertyHelper(PropertyInfo property) - { - if (property == null) - { - throw new ArgumentNullException(nameof(property)); - } - - Property = property; - Name = property.Name; - } - - /// - /// Gets the backing . - /// - public PropertyInfo Property { get; } - - /// - /// Gets (or sets in derived types) the property name. - /// - public virtual string Name { get; protected set; } - - /// - /// Gets the property value getter. - /// - public Func ValueGetter - { - get - { - if (_valueGetter == null) - { - _valueGetter = MakeFastPropertyGetter(Property); - } - - return _valueGetter; - } - } - - /// - /// Gets the property value setter. - /// - public Action ValueSetter - { - get - { - if (_valueSetter == null) - { - _valueSetter = MakeFastPropertySetter(Property); - } - - return _valueSetter; - } - } - - /// - /// Returns the property value for the specified . - /// - /// The object whose property value will be returned. - /// The property value. - public object GetValue(object instance) - { - return ValueGetter(instance); - } - - /// - /// Sets the property value for the specified . - /// - /// The object whose property value will be set. - /// The property value. - public void SetValue(object instance, object value) - { - ValueSetter(instance, value); - } - - /// - /// Creates and caches fast property helpers that expose getters for every public get property on the - /// underlying type. - /// - /// The type info to extract property accessors for. - /// A cached array of all public properties of the specified type. - /// - public static PropertyHelper[] GetProperties(TypeInfo typeInfo) - { - return GetProperties(typeInfo.AsType()); - } - - /// - /// Creates and caches fast property helpers that expose getters for every public get property on the - /// specified type. - /// - /// The type to extract property accessors for. - /// A cached array of all public properties of the specified type. - /// - public static PropertyHelper[] GetProperties(Type type) - { - return GetProperties(type, CreateInstance, PropertiesCache); - } - - /// - /// - /// Creates and caches fast property helpers that expose getters for every non-hidden get property - /// on the specified type. - /// - /// - /// excludes properties defined on base types that have been - /// hidden by definitions using the new keyword. - /// - /// - /// The type info to extract property accessors for. - /// - /// A cached array of all public properties of the specified type. - /// - public static PropertyHelper[] GetVisibleProperties(TypeInfo typeInfo) - { - return GetVisibleProperties(typeInfo.AsType(), CreateInstance, PropertiesCache, VisiblePropertiesCache); - } - - /// - /// - /// Creates and caches fast property helpers that expose getters for every non-hidden get property - /// on the specified type. - /// - /// - /// excludes properties defined on base types that have been - /// hidden by definitions using the new keyword. - /// - /// - /// The type to extract property accessors for. - /// - /// A cached array of all public properties of the specified type. - /// - public static PropertyHelper[] GetVisibleProperties(Type type) - { - return GetVisibleProperties(type, CreateInstance, PropertiesCache, VisiblePropertiesCache); - } - - /// - /// Creates a single fast property getter. The result is not cached. - /// - /// propertyInfo to extract the getter for. - /// a fast getter. - /// - /// This method is more memory efficient than a dynamically compiled lambda, and about the - /// same speed. - /// - public static Func MakeFastPropertyGetter(PropertyInfo propertyInfo) - { - Debug.Assert(propertyInfo != null); - - return MakeFastPropertyGetter( - propertyInfo, - CallPropertyGetterOpenGenericMethod, - CallPropertyGetterByReferenceOpenGenericMethod); - } - - /// - /// Creates a single fast property getter which is safe for a null input object. The result is not cached. - /// - /// propertyInfo to extract the getter for. - /// a fast getter. - /// - /// This method is more memory efficient than a dynamically compiled lambda, and about the - /// same speed. - /// - public static Func MakeNullSafeFastPropertyGetter(PropertyInfo propertyInfo) - { - Debug.Assert(propertyInfo != null); - - return MakeFastPropertyGetter( - propertyInfo, - CallNullSafePropertyGetterOpenGenericMethod, - CallNullSafePropertyGetterByReferenceOpenGenericMethod); - } - - private static Func MakeFastPropertyGetter( - PropertyInfo propertyInfo, - MethodInfo propertyGetterWrapperMethod, - MethodInfo propertyGetterByRefWrapperMethod) - { - Debug.Assert(propertyInfo != null); - - // Must be a generic method with a Func<,> parameter - Debug.Assert(propertyGetterWrapperMethod != null); - Debug.Assert(propertyGetterWrapperMethod.IsGenericMethodDefinition); - Debug.Assert(propertyGetterWrapperMethod.GetParameters().Length == 2); - - // Must be a generic method with a ByRefFunc<,> parameter - Debug.Assert(propertyGetterByRefWrapperMethod != null); - Debug.Assert(propertyGetterByRefWrapperMethod.IsGenericMethodDefinition); - Debug.Assert(propertyGetterByRefWrapperMethod.GetParameters().Length == 2); - - var getMethod = propertyInfo.GetMethod; - Debug.Assert(getMethod != null); - Debug.Assert(!getMethod.IsStatic); - Debug.Assert(getMethod.GetParameters().Length == 0); - - // Instance methods in the CLR can be turned into static methods where the first parameter - // is open over "target". This parameter is always passed by reference, so we have a code - // path for value types and a code path for reference types. - if (getMethod.DeclaringType.GetTypeInfo().IsValueType) - { - // Create a delegate (ref TDeclaringType) -> TValue - return MakeFastPropertyGetter( - typeof(ByRefFunc<,>), - getMethod, - propertyGetterByRefWrapperMethod); - } - else - { - // Create a delegate TDeclaringType -> TValue - return MakeFastPropertyGetter( - typeof(Func<,>), - getMethod, - propertyGetterWrapperMethod); - } - } - - private static Func MakeFastPropertyGetter( - Type openGenericDelegateType, - MethodInfo propertyGetMethod, - MethodInfo openGenericWrapperMethod) - { - var typeInput = propertyGetMethod.DeclaringType; - var typeOutput = propertyGetMethod.ReturnType; - - var delegateType = openGenericDelegateType.MakeGenericType(typeInput, typeOutput); - var propertyGetterDelegate = propertyGetMethod.CreateDelegate(delegateType); - - var wrapperDelegateMethod = openGenericWrapperMethod.MakeGenericMethod(typeInput, typeOutput); - var accessorDelegate = wrapperDelegateMethod.CreateDelegate( - typeof(Func), - propertyGetterDelegate); - - return (Func)accessorDelegate; - } - - /// - /// Creates a single fast property setter for reference types. The result is not cached. - /// - /// propertyInfo to extract the setter for. - /// a fast getter. - /// - /// This method is more memory efficient than a dynamically compiled lambda, and about the - /// same speed. This only works for reference types. - /// - public static Action MakeFastPropertySetter(PropertyInfo propertyInfo) - { - Debug.Assert(propertyInfo != null); - Debug.Assert(!propertyInfo.DeclaringType.GetTypeInfo().IsValueType); - - var setMethod = propertyInfo.SetMethod; - Debug.Assert(setMethod != null); - Debug.Assert(!setMethod.IsStatic); - Debug.Assert(setMethod.ReturnType == typeof(void)); - var parameters = setMethod.GetParameters(); - Debug.Assert(parameters.Length == 1); - - // Instance methods in the CLR can be turned into static methods where the first parameter - // is open over "target". This parameter is always passed by reference, so we have a code - // path for value types and a code path for reference types. - var typeInput = setMethod.DeclaringType; - var parameterType = parameters[0].ParameterType; - - // Create a delegate TDeclaringType -> { TDeclaringType.Property = TValue; } - var propertySetterAsAction = - setMethod.CreateDelegate(typeof(Action<,>).MakeGenericType(typeInput, parameterType)); - var callPropertySetterClosedGenericMethod = - CallPropertySetterOpenGenericMethod.MakeGenericMethod(typeInput, parameterType); - var callPropertySetterDelegate = - callPropertySetterClosedGenericMethod.CreateDelegate( - typeof(Action), propertySetterAsAction); - - return (Action)callPropertySetterDelegate; - } - - /// - /// Given an object, adds each instance property with a public get method as a key and its - /// associated value to a dictionary. - /// - /// If the object is already an instance, then a copy - /// is returned. - /// - /// - /// The implementation of PropertyHelper will cache the property accessors per-type. This is - /// faster when the same type is used multiple times with ObjectToDictionary. - /// - public static IDictionary ObjectToDictionary(object value) - { - var dictionary = value as IDictionary; - if (dictionary != null) - { - return new Dictionary(dictionary, StringComparer.OrdinalIgnoreCase); - } - - dictionary = new Dictionary(StringComparer.OrdinalIgnoreCase); - - if (value != null) - { - foreach (var helper in GetProperties(value.GetType())) - { - dictionary[helper.Name] = helper.GetValue(value); - } - } - - return dictionary; - } - - private static PropertyHelper CreateInstance(PropertyInfo property) - { - return new PropertyHelper(property); - } - - // Called via reflection - private static object CallPropertyGetter( - Func getter, - object target) - { - return getter((TDeclaringType)target); - } - - // Called via reflection - private static object CallPropertyGetterByReference( - ByRefFunc getter, - object target) - { - var unboxed = (TDeclaringType)target; - return getter(ref unboxed); - } - - // Called via reflection - private static object CallNullSafePropertyGetter( - Func getter, - object target) - { - if (target == null) - { - return null; - } - - return getter((TDeclaringType)target); - } - - // Called via reflection - private static object CallNullSafePropertyGetterByReference( - ByRefFunc getter, - object target) - { - if (target == null) - { - return null; - } - - var unboxed = (TDeclaringType)target; - return getter(ref unboxed); - } - - private static void CallPropertySetter( - Action setter, - object target, - object value) - { - setter((TDeclaringType)target, (TValue)value); - } - - protected static PropertyHelper[] GetVisibleProperties( - Type type, - Func createPropertyHelper, - ConcurrentDictionary allPropertiesCache, - ConcurrentDictionary visiblePropertiesCache) - { - PropertyHelper[] result; - if (visiblePropertiesCache.TryGetValue(type, out result)) - { - return result; - } - - // The simple and common case, this is normal POCO object - no need to allocate. - var allPropertiesDefinedOnType = true; - var allProperties = GetProperties(type, createPropertyHelper, allPropertiesCache); - foreach (var propertyHelper in allProperties) - { - if (propertyHelper.Property.DeclaringType != type) - { - allPropertiesDefinedOnType = false; - break; - } - } - - if (allPropertiesDefinedOnType) - { - result = allProperties; - visiblePropertiesCache.TryAdd(type, result); - return result; - } - - // There's some inherited properties here, so we need to check for hiding via 'new'. - var filteredProperties = new List(allProperties.Length); - foreach (var propertyHelper in allProperties) - { - var declaringType = propertyHelper.Property.DeclaringType; - if (declaringType == type) - { - filteredProperties.Add(propertyHelper); - continue; - } - - // If this property was declared on a base type then look for the definition closest to the - // the type to see if we should include it. - var ignoreProperty = false; - - // Walk up the hierarchy until we find the type that actually declares this - // PropertyInfo. - var currentTypeInfo = type.GetTypeInfo(); - var declaringTypeInfo = declaringType.GetTypeInfo(); - while (currentTypeInfo != null && currentTypeInfo != declaringTypeInfo) - { - // We've found a 'more proximal' public definition - var declaredProperty = currentTypeInfo.GetDeclaredProperty(propertyHelper.Name); - if (declaredProperty != null) - { - ignoreProperty = true; - break; - } - - currentTypeInfo = currentTypeInfo.BaseType?.GetTypeInfo(); - } - - if (!ignoreProperty) - { - filteredProperties.Add(propertyHelper); - } - } - - result = filteredProperties.ToArray(); - visiblePropertiesCache.TryAdd(type, result); - return result; - } - - protected static PropertyHelper[] GetProperties( - Type type, - Func createPropertyHelper, - ConcurrentDictionary cache) - { - // Unwrap nullable types. This means Nullable.Value and Nullable.HasValue will not be - // part of the sequence of properties returned by this method. - type = Nullable.GetUnderlyingType(type) ?? type; - - PropertyHelper[] helpers; - if (!cache.TryGetValue(type, out helpers)) - { - // We avoid loading indexed properties using the Where statement. - var properties = type.GetRuntimeProperties().Where(IsInterestingProperty); - - var typeInfo = type.GetTypeInfo(); - if (typeInfo.IsInterface) - { - // Reflection does not return information about inherited properties on the interface itself. - properties = properties.Concat(typeInfo.ImplementedInterfaces.SelectMany( - interfaceType => interfaceType.GetRuntimeProperties().Where(IsInterestingProperty))); - } - - helpers = properties.Select(p => createPropertyHelper(p)).ToArray(); - cache.TryAdd(type, helpers); - } - - return helpers; - } - - // Indexed properties are not useful (or valid) for grabbing properties off an object. - private static bool IsInterestingProperty(PropertyInfo property) - { - // For improving application startup time, do not use GetIndexParameters() api early in this check as it - // creates a copy of parameter array and also we would like to check for the presence of a get method - // and short circuit asap. - return property.GetMethod != null && - property.GetMethod.IsPublic && - !property.GetMethod.IsStatic && - property.GetMethod.GetParameters().Length == 0; - } - } -} diff --git a/src/Shared/test/Shared.Tests/Microsoft.AspNetCore.Shared.Tests.csproj b/src/Shared/test/Shared.Tests/Microsoft.AspNetCore.Shared.Tests.csproj index 05b321a6d6..67ff52821b 100644 --- a/src/Shared/test/Shared.Tests/Microsoft.AspNetCore.Shared.Tests.csproj +++ b/src/Shared/test/Shared.Tests/Microsoft.AspNetCore.Shared.Tests.csproj @@ -21,7 +21,6 @@ - diff --git a/src/Shared/test/Shared.Tests/PropertyActivatorTest.cs b/src/Shared/test/Shared.Tests/PropertyActivatorTest.cs deleted file mode 100644 index a5cb1605b3..0000000000 --- a/src/Shared/test/Shared.Tests/PropertyActivatorTest.cs +++ /dev/null @@ -1,187 +0,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.Reflection; -using Xunit; - -namespace Microsoft.Extensions.Internal -{ - public class PropertyActivatorTest - { - [Fact] - public void Activate_InvokesValueAccessorWithExpectedValue() - { - // Arrange - var instance = new TestClass(); - var typeInfo = instance.GetType().GetTypeInfo(); - var property = typeInfo.GetDeclaredProperty("IntProperty"); - var invokedWith = -1; - var activator = new PropertyActivator( - property, - valueAccessor: (val) => - { - invokedWith = val; - return val; - }); - - // Act - activator.Activate(instance, 123); - - // Assert - Assert.Equal(123, invokedWith); - } - - [Fact] - public void Activate_SetsPropertyValue() - { - // Arrange - var instance = new TestClass(); - var typeInfo = instance.GetType().GetTypeInfo(); - var property = typeInfo.GetDeclaredProperty("IntProperty"); - var activator = new PropertyActivator(property, valueAccessor: (val) => val + 1); - - // Act - activator.Activate(instance, 123); - - // Assert - Assert.Equal(124, instance.IntProperty); - } - - [Fact] - public void GetPropertiesToActivate_RestrictsActivatableProperties() - { - // Arrange - var instance = new TestClass(); - var typeInfo = instance.GetType().GetTypeInfo(); - var expectedPropertyInfo = typeInfo.GetDeclaredProperty("ActivatableProperty"); - - // Act - var propertiesToActivate = PropertyActivator.GetPropertiesToActivate( - type: typeof(TestClass), - activateAttributeType: typeof(TestActivateAttribute), - createActivateInfo: - (propertyInfo) => new PropertyActivator(propertyInfo, valueAccessor: (val) => val + 1)); - - // Assert - Assert.Collection( - propertiesToActivate, - (activator) => - { - Assert.Equal(expectedPropertyInfo, activator.PropertyInfo); - }); - } - - [Fact] - public void GetPropertiesToActivate_CanCreateCustomPropertyActivators() - { - // Arrange - var instance = new TestClass(); - var typeInfo = instance.GetType().GetTypeInfo(); - var expectedPropertyInfo = typeInfo.GetDeclaredProperty("IntProperty"); - - // Act - var propertiesToActivate = PropertyActivator.GetPropertiesToActivate( - type: typeof(TestClass), - activateAttributeType: typeof(TestActivateAttribute), - createActivateInfo: - (propertyInfo) => new PropertyActivator(expectedPropertyInfo, valueAccessor: (val) => val + 1)); - - // Assert - Assert.Collection( - propertiesToActivate, - (activator) => - { - Assert.Equal(expectedPropertyInfo, activator.PropertyInfo); - }); - } - - [Fact] - public void GetPropertiesToActivate_ExcludesNonPublic() - { - // Arrange - var instance = new TestClassWithPropertyVisiblity(); - var typeInfo = instance.GetType().GetTypeInfo(); - var expectedPropertyInfo = typeInfo.GetDeclaredProperty("Public"); - - // Act - var propertiesToActivate = PropertyActivator.GetPropertiesToActivate( - typeof(TestClassWithPropertyVisiblity), - typeof(TestActivateAttribute), - (propertyInfo) => new PropertyActivator(propertyInfo, valueAccessor: (val) => val)); - - // Assert - Assert.Single(propertiesToActivate); - Assert.Single(propertiesToActivate, p => p.PropertyInfo == expectedPropertyInfo); - } - - [Fact] - public void GetPropertiesToActivate_IncludesNonPublic() - { - // Arrange - var instance = new TestClassWithPropertyVisiblity(); - var typeInfo = instance.GetType().GetTypeInfo(); - - // Act - var propertiesToActivate = PropertyActivator.GetPropertiesToActivate( - typeof(TestClassWithPropertyVisiblity), - typeof(TestActivateAttribute), - (propertyInfo) => new PropertyActivator(propertyInfo, valueAccessor: (val) => val), - includeNonPublic: true); - - // Assert - Assert.Equal(5, propertiesToActivate.Length); - } - - private class TestClass - { - public int IntProperty { get; set; } - - [TestActivate] - public int ActivatableProperty { get; set; } - - [TestActivate] - public int NoSetterActivatableProperty { get; } - - [TestActivate] - public int this[int something] // Not activatable - { - get - { - return 0; - } - } - - [TestActivate] - public static int StaticActivatablProperty { get; set; } - } - - private class TestClassWithPropertyVisiblity - { - [TestActivate] - public int Public { get; set; } - - [TestActivate] - protected int Protected { get; set; } - - [TestActivate] - internal int Internal { get; set; } - - [TestActivate] - protected internal int ProtectedInternal {get; set; } - - [TestActivate] - private int Private { get; set; } - } - - [AttributeUsage(AttributeTargets.Property)] - private class TestActivateAttribute : Attribute - { - } - - private class ActivationInfo - { - public string Name { get; set; } - } - } -} diff --git a/src/Shared/test/Shared.Tests/PropertyHelperTest.cs b/src/Shared/test/Shared.Tests/PropertyHelperTest.cs deleted file mode 100644 index 19cf08b370..0000000000 --- a/src/Shared/test/Shared.Tests/PropertyHelperTest.cs +++ /dev/null @@ -1,831 +0,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.Collections.Generic; -using System.Linq; -using System.Reflection; -using Xunit; - -namespace Microsoft.Extensions.Internal -{ - public class PropertyHelperTest - { - [Fact] - public void PropertyHelper_ReturnsNameCorrectly() - { - // Arrange - var anonymous = new { foo = "bar" }; - var property = PropertyHelper.GetProperties(anonymous.GetType()).First().Property; - - // Act - var helper = new PropertyHelper(property); - - // Assert - Assert.Equal("foo", property.Name); - Assert.Equal("foo", helper.Name); - } - - [Fact] - public void PropertyHelper_ReturnsValueCorrectly() - { - // Arrange - var anonymous = new { bar = "baz" }; - var property = PropertyHelper.GetProperties(anonymous.GetType()).First().Property; - - // Act - var helper = new PropertyHelper(property); - - // Assert - Assert.Equal("bar", helper.Name); - Assert.Equal("baz", helper.GetValue(anonymous)); - } - - [Fact] - public void PropertyHelper_ReturnsGetterDelegate() - { - // Arrange - var anonymous = new { bar = "baz" }; - var property = PropertyHelper.GetProperties(anonymous.GetType()).First().Property; - - // Act - var helper = new PropertyHelper(property); - - // Assert - Assert.NotNull(helper.ValueGetter); - Assert.Equal("baz", helper.ValueGetter(anonymous)); - } - - [Fact] - public void SetValue_SetsPropertyValue() - { - // Arrange - var expected = "new value"; - var instance = new BaseClass { PropA = "old value" }; - var helper = PropertyHelper.GetProperties( - instance.GetType()).First(prop => prop.Name == "PropA"); - - // Act - helper.SetValue(instance, expected); - - // Assert - Assert.Equal(expected, instance.PropA); - } - - [Fact] - public void PropertyHelper_ReturnsSetterDelegate() - { - // Arrange - var expected = "new value"; - var instance = new BaseClass { PropA = "old value" }; - var helper = PropertyHelper.GetProperties( - instance.GetType()).First(prop => prop.Name == "PropA"); - - // Act and Assert - Assert.NotNull(helper.ValueSetter); - helper.ValueSetter(instance, expected); - - // Assert - Assert.Equal(expected, instance.PropA); - } - - [Fact] - public void PropertyHelper_ReturnsValueCorrectly_ForValueTypes() - { - // Arrange - var anonymous = new { foo = 32 }; - var property = PropertyHelper.GetProperties(anonymous.GetType()).First().Property; - - // Act - var helper = new PropertyHelper(property); - - // Assert - Assert.Equal("foo", helper.Name); - Assert.Equal(32, helper.GetValue(anonymous)); - } - - [Fact] - public void PropertyHelper_ReturnsCachedPropertyHelper() - { - // Arrange - var anonymous = new { foo = "bar" }; - - // Act - var helpers1 = PropertyHelper.GetProperties(anonymous.GetType().GetTypeInfo()); - var helpers2 = PropertyHelper.GetProperties(anonymous.GetType().GetTypeInfo()); - - // Assert - Assert.Single(helpers1); - Assert.Same(helpers1, helpers2); - Assert.Same(helpers1[0], helpers2[0]); - } - - [Fact] - public void PropertyHelper_DoesNotChangeUnderscores() - { - // Arrange - var anonymous = new { bar_baz2 = "foo" }; - - // Act + Assert - var helper = Assert.Single(PropertyHelper.GetProperties(anonymous.GetType().GetTypeInfo())); - Assert.Equal("bar_baz2", helper.Name); - } - - [Fact] - public void PropertyHelper_DoesNotFindPrivateProperties() - { - // Arrange - var anonymous = new PrivateProperties(); - - // Act + Assert - var helper = Assert.Single(PropertyHelper.GetProperties(anonymous.GetType().GetTypeInfo())); - Assert.Equal("Prop1", helper.Name); - } - - [Fact] - public void PropertyHelper_DoesNotFindStaticProperties() - { - // Arrange - var anonymous = new Static(); - - // Act + Assert - var helper = Assert.Single(PropertyHelper.GetProperties(anonymous.GetType().GetTypeInfo())); - Assert.Equal("Prop5", helper.Name); - } - - [Fact] - public void PropertyHelper_DoesNotFindSetOnlyProperties() - { - // Arrange - var anonymous = new SetOnly(); - - // Act + Assert - var helper = Assert.Single(PropertyHelper.GetProperties(anonymous.GetType().GetTypeInfo())); - Assert.Equal("Prop6", helper.Name); - } - - [Theory] - [InlineData(typeof(int?))] - [InlineData(typeof(DayOfWeek?))] - public void PropertyHelper_WorksForNullablePrimitiveAndEnumTypes(Type nullableType) - { - // Act - var properties = PropertyHelper.GetProperties(nullableType); - - // Assert - Assert.Empty(properties); - } - - [Fact] - public void PropertyHelper_UnwrapsNullableTypes() - { - // Arrange - var myType = typeof(MyStruct?); - - // Act - var properties = PropertyHelper.GetProperties(myType); - - // Assert - var property = Assert.Single(properties); - Assert.Equal("Foo", property.Name); - } - - [Fact] - public void PropertyHelper_WorksForStruct() - { - // Arrange - var anonymous = new MyProperties(); - - anonymous.IntProp = 3; - anonymous.StringProp = "Five"; - - // Act + Assert - var helper1 = Assert.Single(PropertyHelper.GetProperties(anonymous.GetType().GetTypeInfo()).Where(prop => prop.Name == "IntProp")); - var helper2 = Assert.Single(PropertyHelper.GetProperties(anonymous.GetType().GetTypeInfo()).Where(prop => prop.Name == "StringProp")); - Assert.Equal(3, helper1.GetValue(anonymous)); - Assert.Equal("Five", helper2.GetValue(anonymous)); - } - - [Fact] - public void PropertyHelper_ForDerivedClass() - { - // Arrange - var derived = new DerivedClass { PropA = "propAValue", PropB = "propBValue" }; - - // Act - var helpers = PropertyHelper.GetProperties(derived.GetType().GetTypeInfo()).ToArray(); - - // Assert - Assert.NotNull(helpers); - Assert.Equal(2, helpers.Length); - - var propAHelper = Assert.Single(helpers.Where(h => h.Name == "PropA")); - var propBHelper = Assert.Single(helpers.Where(h => h.Name == "PropB")); - - Assert.Equal("propAValue", propAHelper.GetValue(derived)); - Assert.Equal("propBValue", propBHelper.GetValue(derived)); - } - - [Fact] - public void PropertyHelper_ForDerivedClass_WithNew() - { - // Arrange - var derived = new DerivedClassWithNew { PropA = "propAValue" }; - - // Act - var helpers = PropertyHelper.GetProperties(derived.GetType().GetTypeInfo()).ToArray(); - - // Assert - Assert.NotNull(helpers); - Assert.Equal(2, helpers.Length); - - var propAHelper = Assert.Single(helpers.Where(h => h.Name == "PropA")); - var propBHelper = Assert.Single(helpers.Where(h => h.Name == "PropB")); - - Assert.Equal("propAValue", propAHelper.GetValue(derived)); - Assert.Equal("Newed", propBHelper.GetValue(derived)); - } - - [Fact] - public void PropertyHelper_ForDerived_WithVirtual() - { - // Arrange - var derived = new DerivedClassWithOverride { PropA = "propAValue", PropB = "propBValue" }; - - // Act - var helpers = PropertyHelper.GetProperties(derived.GetType().GetTypeInfo()).ToArray(); - - // Assert - Assert.NotNull(helpers); - Assert.Equal(2, helpers.Length); - - var propAHelper = Assert.Single(helpers.Where(h => h.Name == "PropA")); - var propBHelper = Assert.Single(helpers.Where(h => h.Name == "PropB")); - - Assert.Equal("OverridenpropAValue", propAHelper.GetValue(derived)); - Assert.Equal("propBValue", propBHelper.GetValue(derived)); - } - - [Fact] - public void PropertyHelper_ForInterface_ReturnsExpectedProperties() - { - // Arrange - var expectedNames = new[] { "Count", "IsReadOnly" }; - - // Act - var helpers = PropertyHelper.GetProperties(typeof(ICollection)); - - // Assert - Assert.Collection( - helpers.OrderBy(helper => helper.Name, StringComparer.Ordinal), - helper => { Assert.Equal(expectedNames[0], helper.Name, StringComparer.Ordinal); }, - helper => { Assert.Equal(expectedNames[1], helper.Name, StringComparer.Ordinal); }); - } - - [Fact] - public void PropertyHelper_ForDerivedInterface_ReturnsAllProperties() - { - // Arrange - var expectedNames = new[] { "Count", "IsReadOnly", "Keys", "Values" }; - - // Act - var helpers = PropertyHelper.GetProperties(typeof(IDictionary)); - - // Assert - Assert.Collection( - helpers.OrderBy(helper => helper.Name, StringComparer.Ordinal), - helper => { Assert.Equal(expectedNames[0], helper.Name, StringComparer.Ordinal); }, - helper => { Assert.Equal(expectedNames[1], helper.Name, StringComparer.Ordinal); }, - helper => { Assert.Equal(expectedNames[2], helper.Name, StringComparer.Ordinal); }, - helper => { Assert.Equal(expectedNames[3], helper.Name, StringComparer.Ordinal); }); - } - - [Fact] - public void GetProperties_ExcludesIndexersAndPropertiesWithoutPublicGetters() - { - // Arrange - var type = typeof(DerivedClassWithNonReadableProperties); - - // Act - var result = PropertyHelper.GetProperties(type).ToArray(); - - // Assert - Assert.Equal(3, result.Length); - Assert.Equal("Visible", result[0].Name); - Assert.Equal("PropA", result[1].Name); - Assert.Equal("PropB", result[2].Name); - } - - [Fact] - public void GetVisibleProperties_NoHiddenProperty() - { - // Arrange - var type = typeof(string); - - // Act - var result = PropertyHelper.GetVisibleProperties(type).ToArray(); - - // Assert - var property = Assert.Single(result); - Assert.Equal("Length", property.Name); - Assert.Equal(typeof(int), property.Property.PropertyType); - } - - [Fact] - public void GetVisibleProperties_HiddenProperty() - { - // Arrange - var type = typeof(DerivedHiddenProperty); - - // Act - var result = PropertyHelper.GetVisibleProperties(type).ToArray(); - - // Assert - Assert.Equal(2, result.Length); - Assert.Equal("Id", result[0].Name); - Assert.Equal(typeof(string), result[0].Property.PropertyType); - Assert.Equal("Name", result[1].Name); - Assert.Equal(typeof(string), result[1].Property.PropertyType); - } - - [Fact] - public void GetVisibleProperties_HiddenProperty_TwoLevels() - { - // Arrange - var type = typeof(DerivedHiddenProperty2); - - // Act - var result = PropertyHelper.GetVisibleProperties(type).ToArray(); - - // Assert - Assert.Equal(2, result.Length); - Assert.Equal("Id", result[0].Name); - Assert.Equal(typeof(Guid), result[0].Property.PropertyType); - Assert.Equal("Name", result[1].Name); - Assert.Equal(typeof(string), result[1].Property.PropertyType); - } - - [Fact] - public void GetVisibleProperties_NoHiddenPropertyWithTypeInfoInput() - { - // Arrange - var type = typeof(string); - - // Act - var result = PropertyHelper.GetVisibleProperties(type.GetTypeInfo()).ToArray(); - - // Assert - var property = Assert.Single(result); - Assert.Equal("Length", property.Name); - Assert.Equal(typeof(int), property.Property.PropertyType); - } - - [Fact] - public void GetVisibleProperties_HiddenPropertyWithTypeInfoInput() - { - // Arrange - var type = typeof(DerivedHiddenProperty); - - // Act - var result = PropertyHelper.GetVisibleProperties(type.GetTypeInfo()).ToArray(); - - // Assert - Assert.Equal(2, result.Length); - Assert.Equal("Id", result[0].Name); - Assert.Equal(typeof(string), result[0].Property.PropertyType); - Assert.Equal("Name", result[1].Name); - Assert.Equal(typeof(string), result[1].Property.PropertyType); - } - - [Fact] - public void GetVisibleProperties_HiddenProperty_TwoLevelsWithTypeInfoInput() - { - // Arrange - var type = typeof(DerivedHiddenProperty2); - - // Act - var result = PropertyHelper.GetVisibleProperties(type.GetTypeInfo()).ToArray(); - - // Assert - Assert.Equal(2, result.Length); - Assert.Equal("Id", result[0].Name); - Assert.Equal(typeof(Guid), result[0].Property.PropertyType); - Assert.Equal("Name", result[1].Name); - Assert.Equal(typeof(string), result[1].Property.PropertyType); - } - - [Fact] - public void MakeFastPropertySetter_SetsPropertyValues_ForPublicAndNobPublicProperties() - { - // Arrange - var instance = new BaseClass(); - var typeInfo = instance.GetType().GetTypeInfo(); - var publicProperty = typeInfo.GetDeclaredProperty("PropA"); - var protectedProperty = typeInfo.GetDeclaredProperty("PropProtected"); - var publicPropertySetter = PropertyHelper.MakeFastPropertySetter(publicProperty); - var protectedPropertySetter = PropertyHelper.MakeFastPropertySetter(protectedProperty); - - // Act - publicPropertySetter(instance, "TestPublic"); - protectedPropertySetter(instance, "TestProtected"); - - // Assert - Assert.Equal("TestPublic", instance.PropA); - Assert.Equal("TestProtected", instance.GetPropProtected()); - } - - [Fact] - public void MakeFastPropertySetter_SetsPropertyValues_ForOverridenProperties() - { - // Arrange - var instance = new DerivedClassWithOverride(); - var typeInfo = instance.GetType().GetTypeInfo(); - var property = typeInfo.GetDeclaredProperty("PropA"); - var propertySetter = PropertyHelper.MakeFastPropertySetter(property); - - // Act - propertySetter(instance, "Test value"); - - // Assert - Assert.Equal("OverridenTest value", instance.PropA); - } - - [Fact] - public void MakeFastPropertySetter_SetsPropertyValues_ForNewedProperties() - { - // Arrange - var instance = new DerivedClassWithNew(); - var typeInfo = instance.GetType().GetTypeInfo(); - var property = typeInfo.GetDeclaredProperty("PropB"); - var propertySetter = PropertyHelper.MakeFastPropertySetter(property); - - // Act - propertySetter(instance, "Test value"); - - // Assert - Assert.Equal("NewedTest value", instance.PropB); - } - - [Fact] - public void MakeFastPropertyGetter_ReferenceType_ForNullObject_Throws() - { - // Arrange - var property = PropertyHelper - .GetProperties(typeof(BaseClass)) - .Single(p => p.Name == nameof(BaseClass.PropA)); - - var accessor = PropertyHelper.MakeFastPropertyGetter(property.Property); - - // Act & Assert - Assert.Throws(() => accessor(null)); - } - - [Fact] - public void MakeFastPropertyGetter_ValueType_ForNullObject_Throws() - { - // Arrange - var property = PropertyHelper - .GetProperties(typeof(MyProperties)) - .Single(p => p.Name == nameof(MyProperties.StringProp)); - - var accessor = PropertyHelper.MakeFastPropertyGetter(property.Property); - - // Act & Assert - Assert.Throws(() => accessor(null)); - } - - [Fact] - public void MakeNullSafeFastPropertyGetter_ReferenceType_Success() - { - // Arrange - var property = PropertyHelper - .GetProperties(typeof(BaseClass)) - .Single(p => p.Name == nameof(BaseClass.PropA)); - - var accessor = PropertyHelper.MakeNullSafeFastPropertyGetter(property.Property); - - // Act - var value = accessor(new BaseClass() { PropA = "Hi" }); - - // Assert - Assert.Equal("Hi", value); - } - - [Fact] - public void MakeNullSafeFastPropertyGetter_ValueType_Success() - { - // Arrange - var property = PropertyHelper - .GetProperties(typeof(MyProperties)) - .Single(p => p.Name == nameof(MyProperties.StringProp)); - - var accessor = PropertyHelper.MakeNullSafeFastPropertyGetter(property.Property); - - // Act - var value = accessor(new MyProperties() { StringProp = "Hi" }); - - // Assert - Assert.Equal("Hi", value); - } - - [Fact] - public void MakeNullSafeFastPropertyGetter_ReferenceType_ForNullObject_ReturnsNull() - { - // Arrange - var property = PropertyHelper - .GetProperties(typeof(BaseClass)) - .Single(p => p.Name == nameof(BaseClass.PropA)); - - var accessor = PropertyHelper.MakeNullSafeFastPropertyGetter(property.Property); - - // Act - var value = accessor(null); - - // Assert - Assert.Null(value); - } - - [Fact] - public void MakeNullSafeFastPropertyGetter_ValueType_ForNullObject_ReturnsNull() - { - // Arrange - var property = PropertyHelper - .GetProperties(typeof(MyProperties)) - .Single(p => p.Name == nameof(MyProperties.StringProp)); - - var accessor = PropertyHelper.MakeNullSafeFastPropertyGetter(property.Property); - - // Act - var value = accessor(null); - - // Assert - Assert.Null(value); - } - - public static TheoryData> IgnoreCaseTestData - { - get - { - return new TheoryData> - { - { - new - { - selected = true, - SeLeCtEd = false - }, - new KeyValuePair("selected", false) - }, - { - new - { - SeLeCtEd = false, - selected = true - }, - new KeyValuePair("SeLeCtEd", true) - }, - { - new - { - SelECTeD = false, - SeLECTED = true - }, - new KeyValuePair("SelECTeD", true) - } - }; - } - } - - [Theory] - [MemberData(nameof(IgnoreCaseTestData))] - public void ObjectToDictionary_IgnoresPropertyCase(object testObject, - KeyValuePair expectedEntry) - { - // Act - var result = PropertyHelper.ObjectToDictionary(testObject); - - // Assert - var entry = Assert.Single(result); - Assert.Equal(expectedEntry, entry); - } - - [Fact] - public void ObjectToDictionary_WithNullObject_ReturnsEmptyDictionary() - { - // Arrange - object value = null; - - // Act - var dictValues = PropertyHelper.ObjectToDictionary(value); - - // Assert - Assert.NotNull(dictValues); - Assert.Equal(0, dictValues.Count); - } - - [Fact] - public void ObjectToDictionary_WithPlainObjectType_ReturnsEmptyDictionary() - { - // Arrange - var value = new object(); - - // Act - var dictValues = PropertyHelper.ObjectToDictionary(value); - - // Assert - Assert.NotNull(dictValues); - Assert.Equal(0, dictValues.Count); - } - - [Fact] - public void ObjectToDictionary_WithPrimitiveType_LooksUpPublicProperties() - { - // Arrange - var value = "test"; - - // Act - var dictValues = PropertyHelper.ObjectToDictionary(value); - - // Assert - Assert.NotNull(dictValues); - Assert.Equal(1, dictValues.Count); - Assert.Equal(4, dictValues["Length"]); - } - - [Fact] - public void ObjectToDictionary_WithAnonymousType_LooksUpProperties() - { - // Arrange - var value = new { test = "value", other = 1 }; - - // Act - var dictValues = PropertyHelper.ObjectToDictionary(value); - - // Assert - Assert.NotNull(dictValues); - Assert.Equal(2, dictValues.Count); - Assert.Equal("value", dictValues["test"]); - Assert.Equal(1, dictValues["other"]); - } - - [Fact] - public void ObjectToDictionary_ReturnsCaseInsensitiveDictionary() - { - // Arrange - var value = new { TEST = "value", oThEr = 1 }; - - // Act - var dictValues = PropertyHelper.ObjectToDictionary(value); - - // Assert - Assert.NotNull(dictValues); - Assert.Equal(2, dictValues.Count); - Assert.Equal("value", dictValues["test"]); - Assert.Equal(1, dictValues["other"]); - } - - [Fact] - public void ObjectToDictionary_ReturnsInheritedProperties() - { - // Arrange - var value = new ThreeDPoint() { X = 5, Y = 10, Z = 17 }; - - // Act - var dictValues = PropertyHelper.ObjectToDictionary(value); - - // Assert - Assert.NotNull(dictValues); - Assert.Equal(3, dictValues.Count); - Assert.Equal(5, dictValues["X"]); - Assert.Equal(10, dictValues["Y"]); - Assert.Equal(17, dictValues["Z"]); - } - - private class Point - { - public int X { get; set; } - public int Y { get; set; } - } - - private class ThreeDPoint : Point - { - public int Z { get; set; } - } - - private class Static - { - public static int Prop2 { get; set; } - public int Prop5 { get; set; } - } - - private struct MyProperties - { - public int IntProp { get; set; } - public string StringProp { get; set; } - } - - private class SetOnly - { - public int Prop2 { set { } } - public int Prop6 { get; set; } - } - - private class PrivateProperties - { - public int Prop1 { get; set; } - protected int Prop2 { get; set; } - private int Prop3 { get; set; } - } - - public class BaseClass - { - public string PropA { get; set; } - - protected string PropProtected { get; set; } - - public string GetPropProtected() - { - return PropProtected; - } - } - - public class DerivedClass : BaseClass - { - public string PropB { get; set; } - } - - public class BaseClassWithVirtual - { - public virtual string PropA { get; set; } - public string PropB { get; set; } - } - - public class DerivedClassWithNew : BaseClassWithVirtual - { - private string _value = "Newed"; - - public new string PropB - { - get { return _value; } - set { _value = "Newed" + value; } - } - } - - public class DerivedClassWithOverride : BaseClassWithVirtual - { - private string _value = "Overriden"; - - public override string PropA - { - get { return _value; } - set { _value = "Overriden" + value; } - } - } - - private class DerivedClassWithNonReadableProperties : BaseClassWithVirtual - { - public string this[int index] - { - get { return string.Empty; } - set { } - } - - public int Visible { get; set; } - - private string NotVisible { get; set; } - - public string NotVisible2 { private get; set; } - - public string NotVisible3 - { - set { } - } - - public static string NotVisible4 { get; set; } - } - - private struct MyStruct - { - public string Foo { get; set; } - } - - private class BaseHiddenProperty - { - public int Id { get; set; } - } - - private class DerivedHiddenProperty : BaseHiddenProperty - { - public new string Id { get; set; } - - public string Name { get; set; } - } - - private class DerivedHiddenProperty2 : DerivedHiddenProperty - { - public new Guid Id { get; set; } - - public new string Name { get; private set; } - } - } -} From e0051303d1c1147665f88b83ce721294538da131 Mon Sep 17 00:00:00 2001 From: Nate McMaster Date: Thu, 13 Dec 2018 11:19:00 -0800 Subject: [PATCH 0033/1101] Remove Microsoft.Extensions.RazorViews.Sources \n\nCommit migrated from https://github.com/dotnet/extensions/commit/3f5df6ddbc64e49290f969909378b5138e12ce93 --- src/Shared/RazorViews/AttributeValue.cs | 38 --- src/Shared/RazorViews/BaseView.cs | 279 ------------------ src/Shared/RazorViews/HelperResult.cs | 34 --- .../Microsoft.AspNetCore.Shared.Tests.csproj | 1 - 4 files changed, 352 deletions(-) delete mode 100644 src/Shared/RazorViews/AttributeValue.cs delete mode 100644 src/Shared/RazorViews/BaseView.cs delete mode 100644 src/Shared/RazorViews/HelperResult.cs diff --git a/src/Shared/RazorViews/AttributeValue.cs b/src/Shared/RazorViews/AttributeValue.cs deleted file mode 100644 index 7a066a7040..0000000000 --- a/src/Shared/RazorViews/AttributeValue.cs +++ /dev/null @@ -1,38 +0,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; - -namespace Microsoft.Extensions.RazorViews -{ - internal class AttributeValue - { - public AttributeValue(string prefix, object value, bool literal) - { - Prefix = prefix; - Value = value; - Literal = literal; - } - - public string Prefix { get; } - - public object Value { get; } - - public bool Literal { get; } - - public static AttributeValue FromTuple(Tuple value) - { - return new AttributeValue(value.Item1, value.Item2, value.Item3); - } - - public static AttributeValue FromTuple(Tuple value) - { - return new AttributeValue(value.Item1, value.Item2, value.Item3); - } - - public static implicit operator AttributeValue(Tuple value) - { - return FromTuple(value); - } - } -} \ No newline at end of file diff --git a/src/Shared/RazorViews/BaseView.cs b/src/Shared/RazorViews/BaseView.cs deleted file mode 100644 index a171d8d1f2..0000000000 --- a/src/Shared/RazorViews/BaseView.cs +++ /dev/null @@ -1,279 +0,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.Collections.Generic; -using System.Diagnostics; -using System.Globalization; -using System.IO; -using System.Linq; -using System.Text; -using System.Text.Encodings.Web; -using System.Threading.Tasks; -using Microsoft.AspNetCore.Http; - -namespace Microsoft.Extensions.RazorViews -{ - /// - /// Infrastructure - /// - internal abstract class BaseView - { - private static readonly Encoding UTF8NoBOM = new UTF8Encoding(encoderShouldEmitUTF8Identifier: false, throwOnInvalidBytes: true); - private readonly Stack _textWriterStack = new Stack(); - - /// - /// The request context - /// - protected HttpContext Context { get; private set; } - - /// - /// The request - /// - protected HttpRequest Request { get; private set; } - - /// - /// The response - /// - protected HttpResponse Response { get; private set; } - - /// - /// The output stream - /// - protected TextWriter Output { get; private set; } - - /// - /// Html encoder used to encode content. - /// - protected HtmlEncoder HtmlEncoder { get; set; } = HtmlEncoder.Default; - - /// - /// Url encoder used to encode content. - /// - protected UrlEncoder UrlEncoder { get; set; } = UrlEncoder.Default; - - /// - /// JavaScript encoder used to encode content. - /// - protected JavaScriptEncoder JavaScriptEncoder { get; set; } = JavaScriptEncoder.Default; - - /// - /// Execute an individual request - /// - /// - public async Task ExecuteAsync(HttpContext context) - { - Context = context; - Request = Context.Request; - Response = Context.Response; - Output = new StreamWriter(Response.Body, UTF8NoBOM, 4096, leaveOpen: true); - await ExecuteAsync(); - Output.Dispose(); - } - - /// - /// Execute an individual request - /// - public abstract Task ExecuteAsync(); - - protected virtual void PushWriter(TextWriter writer) - { - if (writer == null) - { - throw new ArgumentNullException(nameof(writer)); - } - - _textWriterStack.Push(Output); - Output = writer; - } - - protected virtual TextWriter PopWriter() - { - Output = _textWriterStack.Pop(); - return Output; - } - - /// - /// Write the given value without HTML encoding directly to . - /// - /// The to write. - protected void WriteLiteral(object value) - { - WriteLiteral(Convert.ToString(value, CultureInfo.InvariantCulture)); - } - - /// - /// Write the given value without HTML encoding directly to . - /// - /// The to write. - protected void WriteLiteral(string value) - { - if (!string.IsNullOrEmpty(value)) - { - Output.Write(value); - } - } - - private List AttributeValues { get; set; } - - protected void WriteAttributeValue(string thingy, int startPostion, object value, int endValue, int dealyo, bool yesno) - { - if (AttributeValues == null) - { - AttributeValues = new List(); - } - - AttributeValues.Add(value.ToString()); - } - - private string AttributeEnding { get; set; } - - protected void BeginWriteAttribute(string name, string begining, int startPosition, string ending, int endPosition, int thingy) - { - Debug.Assert(string.IsNullOrEmpty(AttributeEnding)); - - Output.Write(begining); - AttributeEnding = ending; - } - - protected void EndWriteAttribute() - { - Debug.Assert(!string.IsNullOrEmpty(AttributeEnding)); - - var attributes = string.Join(" ", AttributeValues); - Output.Write(attributes); - AttributeValues = null; - - Output.Write(AttributeEnding); - AttributeEnding = null; - } - - /// - /// Writes the given attribute to the given writer - /// - /// The name of the attribute to write - /// The value of the prefix - /// The value of the suffix - /// The s to write. - protected void WriteAttribute( - string name, - string leader, - string trailer, - params AttributeValue[] values) - { - if (name == null) - { - throw new ArgumentNullException(nameof(name)); - } - - if (leader == null) - { - throw new ArgumentNullException(nameof(leader)); - } - - if (trailer == null) - { - throw new ArgumentNullException(nameof(trailer)); - } - - WriteLiteral(leader); - foreach (var value in values) - { - WriteLiteral(value.Prefix); - - // The special cases here are that the value we're writing might already be a string, or that the - // value might be a bool. If the value is the bool 'true' we want to write the attribute name - // instead of the string 'true'. If the value is the bool 'false' we don't want to write anything. - // Otherwise the value is another object (perhaps an HtmlString) and we'll ask it to format itself. - string stringValue; - if (value.Value is bool) - { - if ((bool)value.Value) - { - stringValue = name; - } - else - { - continue; - } - } - else - { - stringValue = value.Value as string; - } - - // Call the WriteTo(string) overload when possible - if (value.Literal && stringValue != null) - { - WriteLiteral(stringValue); - } - else if (value.Literal) - { - WriteLiteral(value.Value); - } - else if (stringValue != null) - { - Write(stringValue); - } - else - { - Write(value.Value); - } - } - WriteLiteral(trailer); - } - - /// - /// is invoked - /// - /// The to invoke - protected void Write(HelperResult result) - { - Write(result); - } - - /// - /// Writes the specified to . - /// - /// The to write. - /// - /// is invoked for types. - /// For all other types, the encoded result of is written to - /// . - /// - protected void Write(object value) - { - if (value is HelperResult helperResult) - { - helperResult.WriteTo(Output); - } - else - { - Write(Convert.ToString(value, CultureInfo.InvariantCulture)); - } - } - - /// - /// Writes the specified with HTML encoding to . - /// - /// The to write. - protected void Write(string value) - { - WriteLiteral(HtmlEncoder.Encode(value)); - } - - protected string HtmlEncodeAndReplaceLineBreaks(string input) - { - if (string.IsNullOrEmpty(input)) - { - return string.Empty; - } - - // Split on line breaks before passing it through the encoder. - return string.Join("
" + Environment.NewLine, - input.Split(new[] { "\r\n" }, StringSplitOptions.None) - .SelectMany(s => s.Split(new[] { '\r', '\n' }, StringSplitOptions.None)) - .Select(HtmlEncoder.Encode)); - } - } -} \ No newline at end of file diff --git a/src/Shared/RazorViews/HelperResult.cs b/src/Shared/RazorViews/HelperResult.cs deleted file mode 100644 index c79944aae6..0000000000 --- a/src/Shared/RazorViews/HelperResult.cs +++ /dev/null @@ -1,34 +0,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.IO; - -namespace Microsoft.Extensions.RazorViews -{ - /// - /// Represents a deferred write operation in a . - /// - internal class HelperResult - { - /// - /// Creates a new instance of . - /// - /// The delegate to invoke when is called. - public HelperResult(Action action) - { - WriteAction = action; - } - - public Action WriteAction { get; } - - /// - /// Method invoked to produce content from the . - /// - /// The instance to write to. - public void WriteTo(TextWriter writer) - { - WriteAction(writer); - } - } -} \ No newline at end of file diff --git a/src/Shared/test/Shared.Tests/Microsoft.AspNetCore.Shared.Tests.csproj b/src/Shared/test/Shared.Tests/Microsoft.AspNetCore.Shared.Tests.csproj index 67ff52821b..014c17aba3 100644 --- a/src/Shared/test/Shared.Tests/Microsoft.AspNetCore.Shared.Tests.csproj +++ b/src/Shared/test/Shared.Tests/Microsoft.AspNetCore.Shared.Tests.csproj @@ -10,7 +10,6 @@ From 01a4a273d7fcaa1714e249786ee6f88b004de73e Mon Sep 17 00:00:00 2001 From: Nate McMaster Date: Thu, 13 Dec 2018 11:26:34 -0800 Subject: [PATCH 0034/1101] Remove Microsoft.Extensions.SecurityHelper.Sources \n\nCommit migrated from https://github.com/dotnet/extensions/commit/c41abe44afd7890ae81abef88263a9c8a782f68b --- src/Shared/SecurityHelper/SecurityHelper.cs | 40 -------- .../test/Shared.Tests/SecurityHelperTests.cs | 93 ------------------- 2 files changed, 133 deletions(-) delete mode 100644 src/Shared/SecurityHelper/SecurityHelper.cs delete mode 100644 src/Shared/test/Shared.Tests/SecurityHelperTests.cs diff --git a/src/Shared/SecurityHelper/SecurityHelper.cs b/src/Shared/SecurityHelper/SecurityHelper.cs deleted file mode 100644 index 408ef6b224..0000000000 --- a/src/Shared/SecurityHelper/SecurityHelper.cs +++ /dev/null @@ -1,40 +0,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.Linq; -using System.Security.Claims; - -namespace Microsoft.Extensions.Internal -{ - /// - /// Helper code used when implementing authentication middleware - /// - internal static class SecurityHelper - { - /// - /// Add all ClaimsIdentities from an additional ClaimPrincipal to the ClaimsPrincipal - /// Merges a new claims principal, placing all new identities first, and eliminating - /// any empty unauthenticated identities from context.User - /// - /// The containing existing . - /// The containing to be added. - public static ClaimsPrincipal MergeUserPrincipal(ClaimsPrincipal existingPrincipal, ClaimsPrincipal additionalPrincipal) - { - var newPrincipal = new ClaimsPrincipal(); - - // New principal identities go first - if (additionalPrincipal != null) - { - newPrincipal.AddIdentities(additionalPrincipal.Identities); - } - - // Then add any existing non empty or authenticated identities - if (existingPrincipal != null) - { - newPrincipal.AddIdentities(existingPrincipal.Identities.Where(i => i.IsAuthenticated || i.Claims.Any())); - } - return newPrincipal; - } - } -} diff --git a/src/Shared/test/Shared.Tests/SecurityHelperTests.cs b/src/Shared/test/Shared.Tests/SecurityHelperTests.cs deleted file mode 100644 index 8e7515ad36..0000000000 --- a/src/Shared/test/Shared.Tests/SecurityHelperTests.cs +++ /dev/null @@ -1,93 +0,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.Linq; -using System.Security.Claims; -using System.Security.Principal; -using Xunit; - -namespace Microsoft.Extensions.Internal -{ - public class SecurityHelperTests - { - [Fact] - public void AddingToAnonymousIdentityDoesNotKeepAnonymousIdentity() - { - var user = SecurityHelper.MergeUserPrincipal(new ClaimsPrincipal(), new GenericPrincipal(new GenericIdentity("Test1", "Alpha"), new string[0])); - - Assert.NotNull(user); - Assert.Equal("Alpha", user.Identity.AuthenticationType); - Assert.Equal("Test1", user.Identity.Name); - Assert.IsAssignableFrom(user); - Assert.IsAssignableFrom(user.Identity); - Assert.Single(user.Identities); - } - - [Fact] - public void AddingExistingIdentityChangesDefaultButPreservesPrior() - { - ClaimsPrincipal user = new GenericPrincipal(new GenericIdentity("Test1", "Alpha"), null); - - Assert.Equal("Alpha", user.Identity.AuthenticationType); - Assert.Equal("Test1", user.Identity.Name); - - user = SecurityHelper.MergeUserPrincipal(user, new GenericPrincipal(new GenericIdentity("Test2", "Beta"), new string[0])); - - Assert.Equal("Beta", user.Identity.AuthenticationType); - Assert.Equal("Test2", user.Identity.Name); - - user = SecurityHelper.MergeUserPrincipal(user, new GenericPrincipal(new GenericIdentity("Test3", "Gamma"), new string[0])); - - Assert.Equal("Gamma", user.Identity.AuthenticationType); - Assert.Equal("Test3", user.Identity.Name); - - Assert.Equal(3, user.Identities.Count()); - Assert.Equal("Test3", user.Identities.Skip(0).First().Name); - Assert.Equal("Test2", user.Identities.Skip(1).First().Name); - Assert.Equal("Test1", user.Identities.Skip(2).First().Name); - } - - [Fact] - public void AddingPreservesNewIdentitiesAndDropsEmpty() - { - var existingPrincipal = new ClaimsPrincipal(new ClaimsIdentity()); - var identityNoAuthTypeWithClaim = new ClaimsIdentity(); - identityNoAuthTypeWithClaim.AddClaim(new Claim("identityNoAuthTypeWithClaim", "yes")); - existingPrincipal.AddIdentity(identityNoAuthTypeWithClaim); - var identityEmptyWithAuthType = new ClaimsIdentity("empty"); - existingPrincipal.AddIdentity(identityEmptyWithAuthType); - - Assert.False(existingPrincipal.Identity.IsAuthenticated); - - var newPrincipal = new ClaimsPrincipal(); - var newEmptyIdentity = new ClaimsIdentity(); - var identityTwo = new ClaimsIdentity("yep"); - newPrincipal.AddIdentity(newEmptyIdentity); - newPrincipal.AddIdentity(identityTwo); - - var user = SecurityHelper.MergeUserPrincipal(existingPrincipal, newPrincipal); - - // Preserve newPrincipal order - Assert.False(user.Identity.IsAuthenticated); - Assert.Null(user.Identity.Name); - - Assert.Equal(4, user.Identities.Count()); - Assert.Equal(newEmptyIdentity, user.Identities.Skip(0).First()); - Assert.Equal(identityTwo, user.Identities.Skip(1).First()); - Assert.Equal(identityNoAuthTypeWithClaim, user.Identities.Skip(2).First()); - Assert.Equal(identityEmptyWithAuthType, user.Identities.Skip(3).First()); - - // This merge should drop newEmptyIdentity since its empty - user = SecurityHelper.MergeUserPrincipal(user, new GenericPrincipal(new GenericIdentity("Test3", "Gamma"), new string[0])); - - Assert.Equal("Gamma", user.Identity.AuthenticationType); - Assert.Equal("Test3", user.Identity.Name); - - Assert.Equal(4, user.Identities.Count()); - Assert.Equal("Test3", user.Identities.Skip(0).First().Name); - Assert.Equal(identityTwo, user.Identities.Skip(1).First()); - Assert.Equal(identityNoAuthTypeWithClaim, user.Identities.Skip(2).First()); - Assert.Equal(identityEmptyWithAuthType, user.Identities.Skip(3).First()); - } - } -} From cb018faf533439473089e227268c8f69a9c44b76 Mon Sep 17 00:00:00 2001 From: Nate McMaster Date: Thu, 13 Dec 2018 11:37:00 -0800 Subject: [PATCH 0035/1101] Remove Microsoft.Extensions.StackTrace.Sources \n\nCommit migrated from https://github.com/dotnet/extensions/commit/1713ada32d3543e17350381f8477ce40715bfd59 --- .../ExceptionDetails/ExceptionDetails.cs | 29 -- .../ExceptionDetailsProvider.cs | 170 --------- .../StackFrame/MethodDisplayInfo.cs | 49 --- .../StackFrame/ParameterDisplayInfo.cs | 33 -- .../StackFrame/PortablePdbReader.cs | 135 ------- .../StackTrace/StackFrame/StackFrameInfo.cs | 18 - .../StackFrame/StackFrameSourceCodeInfo.cs | 54 --- .../StackTrace/StackFrame/StackTraceHelper.cs | 261 ------------- .../Microsoft.AspNetCore.Shared.Tests.csproj | 7 - src/Shared/test/Shared.Tests/Readme.txt | 4 - .../test/Shared.Tests/StackTraceHelperTest.cs | 345 ------------------ .../testassets/ThrowingLibrary/Thrower.cs | 20 - .../ThrowingLibrary/ThrowingLibrary.csproj | 8 - 13 files changed, 1133 deletions(-) delete mode 100644 src/Shared/StackTrace/ExceptionDetails/ExceptionDetails.cs delete mode 100644 src/Shared/StackTrace/ExceptionDetails/ExceptionDetailsProvider.cs delete mode 100644 src/Shared/StackTrace/StackFrame/MethodDisplayInfo.cs delete mode 100644 src/Shared/StackTrace/StackFrame/ParameterDisplayInfo.cs delete mode 100644 src/Shared/StackTrace/StackFrame/PortablePdbReader.cs delete mode 100644 src/Shared/StackTrace/StackFrame/StackFrameInfo.cs delete mode 100644 src/Shared/StackTrace/StackFrame/StackFrameSourceCodeInfo.cs delete mode 100644 src/Shared/StackTrace/StackFrame/StackTraceHelper.cs delete mode 100644 src/Shared/test/Shared.Tests/Readme.txt delete mode 100644 src/Shared/test/Shared.Tests/StackTraceHelperTest.cs delete mode 100644 src/Shared/test/testassets/ThrowingLibrary/Thrower.cs delete mode 100644 src/Shared/test/testassets/ThrowingLibrary/ThrowingLibrary.csproj diff --git a/src/Shared/StackTrace/ExceptionDetails/ExceptionDetails.cs b/src/Shared/StackTrace/ExceptionDetails/ExceptionDetails.cs deleted file mode 100644 index 8862611136..0000000000 --- a/src/Shared/StackTrace/ExceptionDetails/ExceptionDetails.cs +++ /dev/null @@ -1,29 +0,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.Collections.Generic; - -namespace Microsoft.Extensions.StackTrace.Sources -{ - /// - /// Contains details for individual exception messages. - /// - internal class ExceptionDetails - { - /// - /// An individual exception - /// - public Exception Error { get; set; } - - /// - /// The generated stack frames - /// - public IEnumerable StackFrames { get; set; } - - /// - /// Gets or sets the summary message. - /// - public string ErrorMessage { get; set; } - } -} diff --git a/src/Shared/StackTrace/ExceptionDetails/ExceptionDetailsProvider.cs b/src/Shared/StackTrace/ExceptionDetails/ExceptionDetailsProvider.cs deleted file mode 100644 index 2d1dd20710..0000000000 --- a/src/Shared/StackTrace/ExceptionDetails/ExceptionDetailsProvider.cs +++ /dev/null @@ -1,170 +0,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.Collections.Generic; -using System.IO; -using System.Linq; -using System.Reflection; -using Microsoft.Extensions.FileProviders; - -namespace Microsoft.Extensions.StackTrace.Sources -{ - internal class ExceptionDetailsProvider - { - private readonly IFileProvider _fileProvider; - private readonly int _sourceCodeLineCount; - - public ExceptionDetailsProvider(IFileProvider fileProvider, int sourceCodeLineCount) - { - _fileProvider = fileProvider; - _sourceCodeLineCount = sourceCodeLineCount; - } - - public IEnumerable GetDetails(Exception exception) - { - var exceptions = FlattenAndReverseExceptionTree(exception); - - foreach (var ex in exceptions) - { - yield return new ExceptionDetails - { - Error = ex, - StackFrames = StackTraceHelper.GetFrames(ex) - .Select(frame => GetStackFrameSourceCodeInfo( - frame.MethodDisplayInfo.ToString(), - frame.FilePath, - frame.LineNumber)) - }; - } - } - - private static IEnumerable FlattenAndReverseExceptionTree(Exception ex) - { - // ReflectionTypeLoadException is special because the details are in - // the LoaderExceptions property - var typeLoadException = ex as ReflectionTypeLoadException; - if (typeLoadException != null) - { - var typeLoadExceptions = new List(); - foreach (var loadException in typeLoadException.LoaderExceptions) - { - typeLoadExceptions.AddRange(FlattenAndReverseExceptionTree(loadException)); - } - - typeLoadExceptions.Add(ex); - return typeLoadExceptions; - } - - var list = new List(); - if (ex is AggregateException aggregateException) - { - list.Add(ex); - foreach (var innerException in aggregateException.Flatten().InnerExceptions) - { - list.Add(innerException); - } - } - - else - { - while (ex != null) - { - list.Add(ex); - ex = ex.InnerException; - } - list.Reverse(); - } - - return list; - } - - // make it internal to enable unit testing - internal StackFrameSourceCodeInfo GetStackFrameSourceCodeInfo(string method, string filePath, int lineNumber) - { - var stackFrame = new StackFrameSourceCodeInfo - { - Function = method, - File = filePath, - Line = lineNumber - }; - - if (string.IsNullOrEmpty(stackFrame.File)) - { - return stackFrame; - } - - IEnumerable lines = null; - if (File.Exists(stackFrame.File)) - { - lines = File.ReadLines(stackFrame.File); - } - else - { - // Handle relative paths and embedded files - var fileInfo = _fileProvider.GetFileInfo(stackFrame.File); - if (fileInfo.Exists) - { - // ReadLines doesn't accept a stream. Use ReadLines as its more efficient - // relative to reading lines via stream reader - if (!string.IsNullOrEmpty(fileInfo.PhysicalPath)) - { - lines = File.ReadLines(fileInfo.PhysicalPath); - } - else - { - lines = ReadLines(fileInfo); - } - } - } - - if (lines != null) - { - ReadFrameContent(stackFrame, lines, stackFrame.Line, stackFrame.Line); - } - - return stackFrame; - } - - // make it internal to enable unit testing - internal void ReadFrameContent( - StackFrameSourceCodeInfo frame, - IEnumerable allLines, - int errorStartLineNumberInFile, - int errorEndLineNumberInFile) - { - // Get the line boundaries in the file to be read and read all these lines at once into an array. - var preErrorLineNumberInFile = Math.Max(errorStartLineNumberInFile - _sourceCodeLineCount, 1); - var postErrorLineNumberInFile = errorEndLineNumberInFile + _sourceCodeLineCount; - var codeBlock = allLines - .Skip(preErrorLineNumberInFile - 1) - .Take(postErrorLineNumberInFile - preErrorLineNumberInFile + 1) - .ToArray(); - - var numOfErrorLines = (errorEndLineNumberInFile - errorStartLineNumberInFile) + 1; - var errorStartLineNumberInArray = errorStartLineNumberInFile - preErrorLineNumberInFile; - - frame.PreContextLine = preErrorLineNumberInFile; - frame.PreContextCode = codeBlock.Take(errorStartLineNumberInArray).ToArray(); - frame.ContextCode = codeBlock - .Skip(errorStartLineNumberInArray) - .Take(numOfErrorLines) - .ToArray(); - frame.PostContextCode = codeBlock - .Skip(errorStartLineNumberInArray + numOfErrorLines) - .ToArray(); - } - - private static IEnumerable ReadLines(IFileInfo fileInfo) - { - using (var reader = new StreamReader(fileInfo.CreateReadStream())) - { - string line; - while ((line = reader.ReadLine()) != null) - { - yield return line; - } - } - } - } -} diff --git a/src/Shared/StackTrace/StackFrame/MethodDisplayInfo.cs b/src/Shared/StackTrace/StackFrame/MethodDisplayInfo.cs deleted file mode 100644 index b1c0ccc188..0000000000 --- a/src/Shared/StackTrace/StackFrame/MethodDisplayInfo.cs +++ /dev/null @@ -1,49 +0,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.Collections.Generic; -using System.Linq; -using System.Text; - -namespace Microsoft.Extensions.StackTrace.Sources -{ - internal class MethodDisplayInfo - { - public string DeclaringTypeName { get; set; } - - public string Name { get; set; } - - public string GenericArguments { get; set; } - - public string SubMethod { get; set; } - - public IEnumerable Parameters { get; set; } - - public override string ToString() - { - var builder = new StringBuilder(); - if (!string.IsNullOrEmpty(DeclaringTypeName)) - { - builder - .Append(DeclaringTypeName) - .Append("."); - } - - builder.Append(Name); - builder.Append(GenericArguments); - - builder.Append("("); - builder.Append(string.Join(", ", Parameters.Select(p => p.ToString()))); - builder.Append(")"); - - if (!string.IsNullOrEmpty(SubMethod)) - { - builder.Append("+"); - builder.Append(SubMethod); - builder.Append("()"); - } - - return builder.ToString(); - } - } -} diff --git a/src/Shared/StackTrace/StackFrame/ParameterDisplayInfo.cs b/src/Shared/StackTrace/StackFrame/ParameterDisplayInfo.cs deleted file mode 100644 index 1199a8386d..0000000000 --- a/src/Shared/StackTrace/StackFrame/ParameterDisplayInfo.cs +++ /dev/null @@ -1,33 +0,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.Text; - -namespace Microsoft.Extensions.StackTrace.Sources -{ - internal class ParameterDisplayInfo - { - public string Name { get; set; } - - public string Type { get; set; } - - public string Prefix { get; set; } - - public override string ToString() - { - var builder = new StringBuilder(); - if (!string.IsNullOrEmpty(Prefix)) - { - builder - .Append(Prefix) - .Append(" "); - } - - builder.Append(Type); - builder.Append(" "); - builder.Append(Name); - - return builder.ToString(); - } - } -} diff --git a/src/Shared/StackTrace/StackFrame/PortablePdbReader.cs b/src/Shared/StackTrace/StackFrame/PortablePdbReader.cs deleted file mode 100644 index ff6a4947f8..0000000000 --- a/src/Shared/StackTrace/StackFrame/PortablePdbReader.cs +++ /dev/null @@ -1,135 +0,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.Collections.Generic; -using System.Diagnostics; -using System.IO; -using System.Reflection; -using System.Reflection.Metadata; -using System.Reflection.Metadata.Ecma335; -using System.Reflection.PortableExecutable; - -namespace Microsoft.Extensions.StackTrace.Sources -{ - internal class PortablePdbReader : IDisposable - { - private readonly Dictionary _cache = - new Dictionary(StringComparer.Ordinal); - - public void PopulateStackFrame(StackFrameInfo frameInfo, MethodBase method, int IlOffset) - { - if (method.Module.Assembly.IsDynamic) - { - return; - } - - var metadataReader = GetMetadataReader(method.Module.Assembly.Location); - - if (metadataReader == null) - { - return; - } - - var methodToken = MetadataTokens.Handle(method.MetadataToken); - - Debug.Assert(methodToken.Kind == HandleKind.MethodDefinition); - - var handle = ((MethodDefinitionHandle)methodToken).ToDebugInformationHandle(); - - if (!handle.IsNil) - { - var methodDebugInfo = metadataReader.GetMethodDebugInformation(handle); - var sequencePoints = methodDebugInfo.GetSequencePoints(); - SequencePoint? bestPointSoFar = null; - - foreach (var point in sequencePoints) - { - if (point.Offset > IlOffset) - { - break; - } - - if (point.StartLine != SequencePoint.HiddenLine) - { - bestPointSoFar = point; - } - } - - if (bestPointSoFar.HasValue) - { - frameInfo.LineNumber = bestPointSoFar.Value.StartLine; - frameInfo.FilePath = metadataReader.GetString(metadataReader.GetDocument(bestPointSoFar.Value.Document).Name); - } - } - } - - private MetadataReader GetMetadataReader(string assemblyPath) - { - MetadataReaderProvider provider = null; - if (!_cache.TryGetValue(assemblyPath, out provider)) - { - var pdbPath = GetPdbPath(assemblyPath); - - if (!string.IsNullOrEmpty(pdbPath) && File.Exists(pdbPath) && IsPortable(pdbPath)) - { - var pdbStream = File.OpenRead(pdbPath); - provider = MetadataReaderProvider.FromPortablePdbStream(pdbStream); - } - - _cache[assemblyPath] = provider; - } - - return provider?.GetMetadataReader(); - } - - private static string GetPdbPath(string assemblyPath) - { - if (string.IsNullOrEmpty(assemblyPath)) - { - return null; - } - - if (File.Exists(assemblyPath)) - { - var peStream = File.OpenRead(assemblyPath); - - using (var peReader = new PEReader(peStream)) - { - foreach (var entry in peReader.ReadDebugDirectory()) - { - if (entry.Type == DebugDirectoryEntryType.CodeView) - { - var codeViewData = peReader.ReadCodeViewDebugDirectoryData(entry); - var peDirectory = Path.GetDirectoryName(assemblyPath); - return Path.Combine(peDirectory, Path.GetFileName(codeViewData.Path)); - } - } - } - } - - return null; - } - - private static bool IsPortable(string pdbPath) - { - using (var pdbStream = File.OpenRead(pdbPath)) - { - return pdbStream.ReadByte() == 'B' && - pdbStream.ReadByte() == 'S' && - pdbStream.ReadByte() == 'J' && - pdbStream.ReadByte() == 'B'; - } - } - - public void Dispose() - { - foreach (var entry in _cache) - { - entry.Value?.Dispose(); - } - - _cache.Clear(); - } - } -} diff --git a/src/Shared/StackTrace/StackFrame/StackFrameInfo.cs b/src/Shared/StackTrace/StackFrame/StackFrameInfo.cs deleted file mode 100644 index ffd91f213c..0000000000 --- a/src/Shared/StackTrace/StackFrame/StackFrameInfo.cs +++ /dev/null @@ -1,18 +0,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.Diagnostics; - -namespace Microsoft.Extensions.StackTrace.Sources -{ - internal class StackFrameInfo - { - public int LineNumber { get; set; } - - public string FilePath { get; set; } - - public StackFrame StackFrame { get; set; } - - public MethodDisplayInfo MethodDisplayInfo { get; set; } - } -} diff --git a/src/Shared/StackTrace/StackFrame/StackFrameSourceCodeInfo.cs b/src/Shared/StackTrace/StackFrame/StackFrameSourceCodeInfo.cs deleted file mode 100644 index 2932e083b1..0000000000 --- a/src/Shared/StackTrace/StackFrame/StackFrameSourceCodeInfo.cs +++ /dev/null @@ -1,54 +0,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.Collections.Generic; -using System.Linq; - -namespace Microsoft.Extensions.StackTrace.Sources -{ - /// - /// Contains the source code where the exception occurred. - /// - internal class StackFrameSourceCodeInfo - { - /// - /// Function containing instruction - /// - public string Function { get; set; } - - /// - /// File containing the instruction - /// - public string File { get; set; } - - /// - /// The line number of the instruction - /// - public int Line { get; set; } - - /// - /// The line preceding the frame line - /// - public int PreContextLine { get; set; } - - /// - /// Lines of code before the actual error line(s). - /// - public IEnumerable PreContextCode { get; set; } = Enumerable.Empty(); - - /// - /// Line(s) of code responsible for the error. - /// - public IEnumerable ContextCode { get; set; } = Enumerable.Empty(); - - /// - /// Lines of code after the actual error line(s). - /// - public IEnumerable PostContextCode { get; set; } = Enumerable.Empty(); - - /// - /// Specific error details for this stack frame. - /// - public string ErrorDetails { get; set; } - } -} diff --git a/src/Shared/StackTrace/StackFrame/StackTraceHelper.cs b/src/Shared/StackTrace/StackFrame/StackTraceHelper.cs deleted file mode 100644 index 5ce9a40903..0000000000 --- a/src/Shared/StackTrace/StackFrame/StackTraceHelper.cs +++ /dev/null @@ -1,261 +0,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.Collections; -using System.Collections.Generic; -using System.Diagnostics; -using System.Linq; -using System.Reflection; -using System.Runtime.CompilerServices; -using System.Runtime.ExceptionServices; -using Microsoft.Extensions.Internal; - -namespace Microsoft.Extensions.StackTrace.Sources -{ - internal class StackTraceHelper - { - public static IList GetFrames(Exception exception) - { - var frames = new List(); - - if (exception == null) - { - return frames; - } - - using (var portablePdbReader = new PortablePdbReader()) - { - var needFileInfo = true; - var stackTrace = new System.Diagnostics.StackTrace(exception, needFileInfo); - var stackFrames = stackTrace.GetFrames(); - - if (stackFrames == null) - { - return frames; - } - - for (var i = 0; i < stackFrames.Length; i++) - { - var frame = stackFrames[i]; - var method = frame.GetMethod(); - - // Always show last stackFrame - if (!ShowInStackTrace(method) && i < stackFrames.Length - 1) - { - continue; - } - - var stackFrame = new StackFrameInfo - { - StackFrame = frame, - FilePath = frame.GetFileName(), - LineNumber = frame.GetFileLineNumber(), - MethodDisplayInfo = GetMethodDisplayString(frame.GetMethod()), - }; - - if (string.IsNullOrEmpty(stackFrame.FilePath)) - { - // .NET Framework and older versions of mono don't support portable PDBs - // so we read it manually to get file name and line information - portablePdbReader.PopulateStackFrame(stackFrame, method, frame.GetILOffset()); - } - - frames.Add(stackFrame); - } - - return frames; - } - } - - internal static MethodDisplayInfo GetMethodDisplayString(MethodBase method) - { - // Special case: no method available - if (method == null) - { - return null; - } - - var methodDisplayInfo = new MethodDisplayInfo(); - - // Type name - var type = method.DeclaringType; - - var methodName = method.Name; - - if (type != null && type.IsDefined(typeof(CompilerGeneratedAttribute)) && - (typeof(IAsyncStateMachine).IsAssignableFrom(type) || typeof(IEnumerator).IsAssignableFrom(type))) - { - // Convert StateMachine methods to correct overload +MoveNext() - if (TryResolveStateMachineMethod(ref method, out type)) - { - methodDisplayInfo.SubMethod = methodName; - } - } - // ResolveStateMachineMethod may have set declaringType to null - if (type != null) - { - methodDisplayInfo.DeclaringTypeName = TypeNameHelper.GetTypeDisplayName(type, includeGenericParameterNames: true); - } - - // Method name - methodDisplayInfo.Name = method.Name; - if (method.IsGenericMethod) - { - var genericArguments = string.Join(", ", method.GetGenericArguments() - .Select(arg => TypeNameHelper.GetTypeDisplayName(arg, fullName: false, includeGenericParameterNames: true))); - methodDisplayInfo.GenericArguments += "<" + genericArguments + ">"; - } - - // Method parameters - methodDisplayInfo.Parameters = method.GetParameters().Select(parameter => - { - var parameterType = parameter.ParameterType; - - var prefix = string.Empty; - if (parameter.IsOut) - { - prefix = "out"; - } - else if (parameterType != null && parameterType.IsByRef) - { - prefix = "ref"; - } - - var parameterTypeString = "?"; - if (parameterType != null) - { - if (parameterType.IsByRef) - { - parameterType = parameterType.GetElementType(); - } - - parameterTypeString = TypeNameHelper.GetTypeDisplayName(parameterType, fullName: false, includeGenericParameterNames: true); - } - - return new ParameterDisplayInfo - { - Prefix = prefix, - Name = parameter.Name, - Type = parameterTypeString, - }; - }); - - return methodDisplayInfo; - } - - private static bool ShowInStackTrace(MethodBase method) - { - Debug.Assert(method != null); - - // Don't show any methods marked with the StackTraceHiddenAttribute - // https://github.com/dotnet/coreclr/pull/14652 - if (HasStackTraceHiddenAttribute(method)) - { - return false; - } - - - var type = method.DeclaringType; - if (type == null) - { - return true; - } - - if (HasStackTraceHiddenAttribute(type)) - { - return false; - } - - // Fallbacks for runtime pre-StackTraceHiddenAttribute - if (type == typeof(ExceptionDispatchInfo) && method.Name == "Throw") - { - return false; - } - else if (type == typeof(TaskAwaiter) || - type == typeof(TaskAwaiter<>) || - type == typeof(ConfiguredTaskAwaitable.ConfiguredTaskAwaiter) || - type == typeof(ConfiguredTaskAwaitable<>.ConfiguredTaskAwaiter)) - { - switch (method.Name) - { - case "HandleNonSuccessAndDebuggerNotification": - case "ThrowForNonSuccess": - case "ValidateEnd": - case "GetResult": - return false; - } - } - - return true; - } - - private static bool TryResolveStateMachineMethod(ref MethodBase method, out Type declaringType) - { - Debug.Assert(method != null); - Debug.Assert(method.DeclaringType != null); - - declaringType = method.DeclaringType; - - var parentType = declaringType.DeclaringType; - if (parentType == null) - { - return false; - } - - var methods = parentType.GetMethods(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Static | BindingFlags.Instance | BindingFlags.DeclaredOnly); - if (methods == null) - { - return false; - } - - foreach (var candidateMethod in methods) - { - var attributes = candidateMethod.GetCustomAttributes(); - if (attributes == null) - { - continue; - } - - foreach (var asma in attributes) - { - if (asma.StateMachineType == declaringType) - { - method = candidateMethod; - declaringType = candidateMethod.DeclaringType; - // Mark the iterator as changed; so it gets the + annotation of the original method - // async statemachines resolve directly to their builder methods so aren't marked as changed - return asma is IteratorStateMachineAttribute; - } - } - } - - return false; - } - - private static bool HasStackTraceHiddenAttribute(MemberInfo memberInfo) - { - IList attributes; - try - { - // Accessing MembmerInfo.GetCustomAttributesData throws for some types (such as types in dynamically generated assemblies). - // We'll skip looking up StackTraceHiddenAttributes on such types. - attributes = memberInfo.GetCustomAttributesData(); - } - catch - { - return false; - } - - for (var i = 0; i < attributes.Count; i++) - { - if (attributes[i].AttributeType.Name == "StackTraceHiddenAttribute") - { - return true; - } - } - - return false; - } - } -} diff --git a/src/Shared/test/Shared.Tests/Microsoft.AspNetCore.Shared.Tests.csproj b/src/Shared/test/Shared.Tests/Microsoft.AspNetCore.Shared.Tests.csproj index 014c17aba3..1873e485a9 100644 --- a/src/Shared/test/Shared.Tests/Microsoft.AspNetCore.Shared.Tests.csproj +++ b/src/Shared/test/Shared.Tests/Microsoft.AspNetCore.Shared.Tests.csproj @@ -2,7 +2,6 @@ $(StandardTestTfms) - portable true @@ -12,15 +11,9 @@ ..\src\BenchmarkRunner\**\*.cs; ..\src\StackTrace\ExceptionDetails\**\*.cs; " /> -
- - - - - diff --git a/src/Shared/test/Shared.Tests/Readme.txt b/src/Shared/test/Shared.Tests/Readme.txt deleted file mode 100644 index b818bd8148..0000000000 --- a/src/Shared/test/Shared.Tests/Readme.txt +++ /dev/null @@ -1,4 +0,0 @@ -NOTE: -1. The tests for 'ExceptionDetailProvider' and 'StackTraceHelper' in project 'Microsoft.Extensions.StackTrace.Sources' are located in Diagnostics - repo. This is because they refer to some packages from FileSystem repo which causes a circular reference and breaks the - build. \ No newline at end of file diff --git a/src/Shared/test/Shared.Tests/StackTraceHelperTest.cs b/src/Shared/test/Shared.Tests/StackTraceHelperTest.cs deleted file mode 100644 index 657a310b6e..0000000000 --- a/src/Shared/test/Shared.Tests/StackTraceHelperTest.cs +++ /dev/null @@ -1,345 +0,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.Collections.Generic; -using System.Linq; -using System.Linq.Expressions; -using System.Runtime.CompilerServices; -using System.Threading.Tasks; -using Microsoft.Extensions.StackTrace.Sources; -using ThrowingLibrary; -using Xunit; - -namespace Microsoft.Extensions.Internal -{ - public class StackTraceHelperTest - { - [Fact] - public void StackTraceHelper_IncludesLineNumbersForFiles() - { - // Arrange - Exception exception = null; - try - { - // Throwing an exception in the current assembly always seems to populate the full stack - // trace regardless of symbol type. Crossing assembly boundaries ensures PortablePdbReader gets used - // on desktop. - Thrower.Throw(); - } - catch (Exception ex) - { - exception = ex; - } - - // Act - var stackFrames = StackTraceHelper.GetFrames(exception); - - // Assert - Assert.Collection(stackFrames, - frame => - { - Assert.Contains("Thrower.cs", frame.FilePath); - Assert.Equal(17, frame.LineNumber); - }, - frame => - { - Assert.Contains("StackTraceHelperTest.cs", frame.FilePath); - }); - } - - [Fact] - public void StackTraceHelper_PrettyPrintsStackTraceForGenericMethods() - { - // Arrange - var exception = Record.Exception(() => GenericMethod(null)); - - // Act - var stackFrames = StackTraceHelper.GetFrames(exception); - - // Assert - var methods = stackFrames.Select(frame => frame.MethodDisplayInfo.ToString()).ToArray(); - Assert.Equal("Microsoft.Extensions.Internal.StackTraceHelperTest.GenericMethod(T val)", methods[0]); - } - - [Fact] - public void StackTraceHelper_PrettyPrintsStackTraceForMethodsWithOutParameters() - { - // Arrange - var exception = Record.Exception(() => MethodWithOutParameter(out var value)); - - // Act - var stackFrames = StackTraceHelper.GetFrames(exception); - - // Assert - var methods = stackFrames.Select(frame => frame.MethodDisplayInfo.ToString()).ToArray(); - Assert.Equal("Microsoft.Extensions.Internal.StackTraceHelperTest.MethodWithOutParameter(out int value)", methods[0]); - } - - [Fact] - public void StackTraceHelper_PrettyPrintsStackTraceForMethodsWithGenericOutParameters() - { - // Arrange - var exception = Record.Exception(() => MethodWithGenericOutParameter("Test", out int value)); - - // Act - var stackFrames = StackTraceHelper.GetFrames(exception); - - // Assert - var methods = stackFrames.Select(frame => frame.MethodDisplayInfo.ToString()).ToArray(); - Assert.Equal("Microsoft.Extensions.Internal.StackTraceHelperTest.MethodWithGenericOutParameter(string a, out TVal value)", methods[0]); - } - - [Fact] - public void StackTraceHelper_PrettyPrintsStackTraceForMethodsWithRefParameters() - { - // Arrange - var value = 0; - var exception = Record.Exception(() => MethodWithRefParameter(ref value)); - - // Act - var stackFrames = StackTraceHelper.GetFrames(exception); - - // Assert - var methods = stackFrames.Select(frame => frame.MethodDisplayInfo.ToString()).ToArray(); - Assert.Equal("Microsoft.Extensions.Internal.StackTraceHelperTest.MethodWithRefParameter(ref int value)", methods[0]); - } - - [Fact] - public void StackTraceHelper_PrettyPrintsStackTraceForMethodsWithGenericRefParameters() - { - // Arrange - var value = 0; - var exception = Record.Exception(() => MethodWithGenericRefParameter(ref value)); - - // Act - var stackFrames = StackTraceHelper.GetFrames(exception); - - // Assert - var methods = stackFrames.Select(frame => frame.MethodDisplayInfo.ToString()).ToArray(); - Assert.Equal("Microsoft.Extensions.Internal.StackTraceHelperTest.MethodWithGenericRefParameter(ref TVal value)", methods[0]); - } - - [Fact] - public void StackTraceHelper_PrettyPrintsStackTraceForMethodsWithNullableParameters() - { - // Arrange - var value = 0; - var exception = Record.Exception(() => MethodWithNullableParameter(value)); - - // Act - var stackFrames = StackTraceHelper.GetFrames(exception); - - // Assert - var methods = stackFrames.Select(frame => frame.MethodDisplayInfo.ToString()).ToArray(); - Assert.Equal("Microsoft.Extensions.Internal.StackTraceHelperTest.MethodWithNullableParameter(Nullable value)", methods[0]); - } - - [Fact] - public void StackTraceHelper_PrettyPrintsStackTraceForMethodsOnGenericTypes() - { - // Arrange - var exception = Record.Exception(() => new GenericClass().Throw(0)); - - // Act - var stackFrames = StackTraceHelper.GetFrames(exception); - - // Assert - var methods = stackFrames.Select(frame => frame.MethodDisplayInfo.ToString()).ToArray(); - Assert.Equal("Microsoft.Extensions.Internal.StackTraceHelperTest+GenericClass.Throw(T parameter)", methods[0]); - } - - [Fact] - public void StackTraceHelper_ProducesReadableOutput() - { - // Arrange - var expectedCallStack = new List() - { - "Microsoft.Extensions.Internal.StackTraceHelperTest.Iterator()+MoveNext()", - "string.Join(string separator, IEnumerable values)", - "Microsoft.Extensions.Internal.StackTraceHelperTest+GenericClass.GenericMethod(ref V value)", - "Microsoft.Extensions.Internal.StackTraceHelperTest.MethodAsync(int value)", - "Microsoft.Extensions.Internal.StackTraceHelperTest.MethodAsync(TValue value)", - "Microsoft.Extensions.Internal.StackTraceHelperTest.Method(string value)", - "Microsoft.Extensions.Internal.StackTraceHelperTest.StackTraceHelper_ProducesReadableOutput()", - }; - - Exception exception = null; - try - { - Method("test"); - } - catch (Exception ex) - { - exception = ex; - } - - // Act - var stackFrames = StackTraceHelper.GetFrames(exception); - var methodNames = stackFrames.Select(stackFrame => stackFrame.MethodDisplayInfo.ToString()).ToArray(); - - // Assert - Assert.Equal(expectedCallStack, methodNames); - } - - [Fact] - public void StackTraceHelper_DoesNotIncludeInstanceMethodsOnTypesWithStackTraceHiddenAttribute() - { - // Arrange - var exception = Record.Exception(() => InvokeMethodOnTypeWithStackTraceHiddenAttribute()); - - // Act - var stackFrames = StackTraceHelper.GetFrames(exception); - - // Assert - var methods = stackFrames.Select(frame => frame.MethodDisplayInfo.ToString()).ToArray(); - Assert.Equal("Microsoft.Extensions.Internal.StackTraceHelperTest.ThrowCore()", methods[0]); - Assert.Equal("Microsoft.Extensions.Internal.StackTraceHelperTest.InvokeMethodOnTypeWithStackTraceHiddenAttribute()", methods[1]); - } - - [Fact] - public void StackTraceHelper_DoesNotIncludeStaticMethodsOnTypesWithStackTraceHiddenAttribute() - { - // Arrange - var exception = Record.Exception(() => InvokeStaticMethodOnTypeWithStackTraceHiddenAttribute()); - - // Act - var stackFrames = StackTraceHelper.GetFrames(exception); - - // Assert - var methods = stackFrames.Select(frame => frame.MethodDisplayInfo.ToString()).ToArray(); - Assert.Equal("Microsoft.Extensions.Internal.StackTraceHelperTest.ThrowCore()", methods[0]); - Assert.Equal("Microsoft.Extensions.Internal.StackTraceHelperTest.InvokeStaticMethodOnTypeWithStackTraceHiddenAttribute()", methods[1]); - } - - [Fact] - public void StackTraceHelper_DoesNotIncludeMethodsWithStackTraceHiddenAttribute() - { - // Arrange - var exception = Record.Exception(() => new TypeWithMethodWithStackTraceHiddenAttribute().Throw()); - - // Act - var stackFrames = StackTraceHelper.GetFrames(exception); - - // Assert - var methods = stackFrames.Select(frame => frame.MethodDisplayInfo.ToString()).ToArray(); - Assert.Equal("Microsoft.Extensions.Internal.StackTraceHelperTest.ThrowCore()", methods[0]); - Assert.Equal("Microsoft.Extensions.Internal.StackTraceHelperTest+TypeWithMethodWithStackTraceHiddenAttribute.Throw()", methods[1]); - } - - [Fact] - public void GetFrames_DoesNotFailForDynamicallyGeneratedAssemblies() - { - // Arrange - var action = (Action)Expression.Lambda( - Expression.Throw( - Expression.New(typeof(Exception)))).Compile(); - var exception = Record.Exception(action); - - // Act - var frames = StackTraceHelper.GetFrames(exception).ToArray(); - - // Assert - var frame = frames[0]; - Assert.Null(frame.FilePath); - Assert.Equal($"lambda_method(Closure )", frame.MethodDisplayInfo.ToString()); - } - - [MethodImpl(MethodImplOptions.NoOptimization | MethodImplOptions.NoInlining)] - async Task MethodAsync(int value) - { - await Task.Delay(0); - return GenericClass.GenericMethod(ref value); - } - - [MethodImpl(MethodImplOptions.NoOptimization | MethodImplOptions.NoInlining)] - async Task MethodAsync(TValue value) - { - return await MethodAsync(1); - } - - [MethodImpl(MethodImplOptions.NoOptimization | MethodImplOptions.NoInlining)] - string Method(string value) - { - return MethodAsync(value).GetAwaiter().GetResult(); - } - - [MethodImpl(MethodImplOptions.NoOptimization | MethodImplOptions.NoInlining)] - static IEnumerable Iterator() - { - yield return "Success"; - throw new Exception(); - } - - [MethodImpl(MethodImplOptions.NoOptimization | MethodImplOptions.NoInlining)] - void MethodWithOutParameter(out int value) => throw new Exception(); - - [MethodImpl(MethodImplOptions.NoOptimization | MethodImplOptions.NoInlining)] - void MethodWithGenericOutParameter(string a, out TVal value) => throw new Exception(); - - [MethodImpl(MethodImplOptions.NoOptimization | MethodImplOptions.NoInlining)] - void MethodWithRefParameter(ref int value) => throw new Exception(); - - [MethodImpl(MethodImplOptions.NoOptimization | MethodImplOptions.NoInlining)] - void MethodWithGenericRefParameter(ref TVal value) => throw new Exception(); - - [MethodImpl(MethodImplOptions.NoOptimization | MethodImplOptions.NoInlining)] - void MethodWithNullableParameter(int? value) => throw new Exception(); - - [MethodImpl(MethodImplOptions.NoOptimization | MethodImplOptions.NoInlining)] - void InvokeMethodOnTypeWithStackTraceHiddenAttribute() => new TypeWithStackTraceHiddenAttribute().Throw(); - - [MethodImpl(MethodImplOptions.NoOptimization | MethodImplOptions.NoInlining)] - void InvokeStaticMethodOnTypeWithStackTraceHiddenAttribute() => TypeWithStackTraceHiddenAttribute.ThrowStatic(); - - class GenericClass - { - [MethodImpl(MethodImplOptions.NoOptimization | MethodImplOptions.NoInlining)] - public static string GenericMethod(ref V value) - { - var returnVal = ""; - for (var i = 0; i < 10; i++) - { - returnVal += string.Join(", ", Iterator()); - } - return returnVal; - } - - [MethodImpl(MethodImplOptions.NoOptimization | MethodImplOptions.NoInlining)] - public void Throw(T parameter) => throw new Exception(); - } - - [MethodImpl(MethodImplOptions.NoOptimization | MethodImplOptions.NoInlining)] - private void GenericMethod(T val) where T : class => throw new Exception(); - - private class StackTraceHiddenAttribute : Attribute - { - } - - [StackTraceHidden] - private class TypeWithStackTraceHiddenAttribute - { - [MethodImpl(MethodImplOptions.NoOptimization | MethodImplOptions.NoInlining)] - public void Throw() => ThrowCore(); - - [MethodImpl(MethodImplOptions.NoOptimization | MethodImplOptions.NoInlining)] - public static void ThrowStatic() => ThrowCore(); - } - - private class TypeWithMethodWithStackTraceHiddenAttribute - { - [MethodImpl(MethodImplOptions.NoOptimization | MethodImplOptions.NoInlining)] - [StackTraceHidden] - public void MethodWithStackTraceHiddenAttribute() - { - ThrowCore(); - } - - [MethodImpl(MethodImplOptions.NoOptimization | MethodImplOptions.NoInlining)] - public void Throw() => MethodWithStackTraceHiddenAttribute(); - } - - [MethodImpl(MethodImplOptions.NoOptimization | MethodImplOptions.NoInlining)] - private static void ThrowCore() => throw new Exception(); - } -} diff --git a/src/Shared/test/testassets/ThrowingLibrary/Thrower.cs b/src/Shared/test/testassets/ThrowingLibrary/Thrower.cs deleted file mode 100644 index babe2387c6..0000000000 --- a/src/Shared/test/testassets/ThrowingLibrary/Thrower.cs +++ /dev/null @@ -1,20 +0,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 ThrowingLibrary -{ - // Throwing an exception in the current assembly always seems to populate the full stack - // trace regardless of symbol type. This type exists to simulate an exception thrown - // across assemblies which is the typical use case for StackTraceHelper. - public static class Thrower - { - [MethodImpl(MethodImplOptions.NoInlining)] - public static void Throw() - { - throw new DivideByZeroException(); - } - } -} diff --git a/src/Shared/test/testassets/ThrowingLibrary/ThrowingLibrary.csproj b/src/Shared/test/testassets/ThrowingLibrary/ThrowingLibrary.csproj deleted file mode 100644 index d77d392873..0000000000 --- a/src/Shared/test/testassets/ThrowingLibrary/ThrowingLibrary.csproj +++ /dev/null @@ -1,8 +0,0 @@ - - - - netstandard2.0 - portable - - - From 4fcd97e4d289f6d798878e960c51f6f59641a4a9 Mon Sep 17 00:00:00 2001 From: Nate McMaster Date: Thu, 13 Dec 2018 11:41:24 -0800 Subject: [PATCH 0036/1101] Remove Microsoft.Extensions.WebEncoders.Sources \n\nCommit migrated from https://github.com/dotnet/extensions/commit/e7bb41b9bfaba1219dfba3dfd80f3a1452311a72 --- .../Properties/EncoderResources.cs | 38 -- src/Shared/WebEncoders/WebEncoders.cs | 388 ------------------ .../test/Shared.Tests/WebEncodersTests.cs | 113 ----- 3 files changed, 539 deletions(-) delete mode 100644 src/Shared/WebEncoders/Properties/EncoderResources.cs delete mode 100644 src/Shared/WebEncoders/WebEncoders.cs delete mode 100644 src/Shared/test/Shared.Tests/WebEncodersTests.cs diff --git a/src/Shared/WebEncoders/Properties/EncoderResources.cs b/src/Shared/WebEncoders/Properties/EncoderResources.cs deleted file mode 100644 index 3474ae82c5..0000000000 --- a/src/Shared/WebEncoders/Properties/EncoderResources.cs +++ /dev/null @@ -1,38 +0,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.Globalization; - -namespace Microsoft.Extensions.WebEncoders.Sources -{ - // TODO using a resx file. project.json, unfortunately, fails to embed resx files when there are also compile items - // in the contentFiles section. Revisit once we convert repos to MSBuild - internal static class EncoderResources - { - /// - /// Invalid {0}, {1} or {2} length. - /// - internal static readonly string WebEncoders_InvalidCountOffsetOrLength = "Invalid {0}, {1} or {2} length."; - - /// - /// Malformed input: {0} is an invalid input length. - /// - internal static readonly string WebEncoders_MalformedInput = "Malformed input: {0} is an invalid input length."; - - /// - /// Invalid {0}, {1} or {2} length. - /// - internal static string FormatWebEncoders_InvalidCountOffsetOrLength(object p0, object p1, object p2) - { - return string.Format(CultureInfo.CurrentCulture, WebEncoders_InvalidCountOffsetOrLength, p0, p1, p2); - } - - /// - /// Malformed input: {0} is an invalid input length. - /// - internal static string FormatWebEncoders_MalformedInput(object p0) - { - return string.Format(CultureInfo.CurrentCulture, WebEncoders_MalformedInput, p0); - } - } -} diff --git a/src/Shared/WebEncoders/WebEncoders.cs b/src/Shared/WebEncoders/WebEncoders.cs deleted file mode 100644 index 17068ae67a..0000000000 --- a/src/Shared/WebEncoders/WebEncoders.cs +++ /dev/null @@ -1,388 +0,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.Diagnostics; -using System.Globalization; -using Microsoft.Extensions.WebEncoders.Sources; - -#if WebEncoders_In_WebUtilities -namespace Microsoft.AspNetCore.WebUtilities -#else -namespace Microsoft.Extensions.Internal -#endif -{ - /// - /// Contains utility APIs to assist with common encoding and decoding operations. - /// -#if WebEncoders_In_WebUtilities - public -#else - internal -#endif - static class WebEncoders - { - private static readonly byte[] EmptyBytes = new byte[0]; - - /// - /// Decodes a base64url-encoded string. - /// - /// The base64url-encoded input to decode. - /// The base64url-decoded form of the input. - /// - /// The input must not contain any whitespace or padding characters. - /// Throws if the input is malformed. - /// - public static byte[] Base64UrlDecode(string input) - { - if (input == null) - { - throw new ArgumentNullException(nameof(input)); - } - - return Base64UrlDecode(input, offset: 0, count: input.Length); - } - - /// - /// Decodes a base64url-encoded substring of a given string. - /// - /// A string containing the base64url-encoded input to decode. - /// The position in at which decoding should begin. - /// The number of characters in to decode. - /// The base64url-decoded form of the input. - /// - /// The input must not contain any whitespace or padding characters. - /// Throws if the input is malformed. - /// - public static byte[] Base64UrlDecode(string input, int offset, int count) - { - if (input == null) - { - throw new ArgumentNullException(nameof(input)); - } - - ValidateParameters(input.Length, nameof(input), offset, count); - - // Special-case empty input - if (count == 0) - { - return EmptyBytes; - } - - // Create array large enough for the Base64 characters, not just shorter Base64-URL-encoded form. - var buffer = new char[GetArraySizeRequiredToDecode(count)]; - - return Base64UrlDecode(input, offset, buffer, bufferOffset: 0, count: count); - } - - /// - /// Decodes a base64url-encoded into a byte[]. - /// - /// A string containing the base64url-encoded input to decode. - /// The position in at which decoding should begin. - /// - /// Scratch buffer to hold the s to decode. Array must be large enough to hold - /// and characters as well as Base64 padding - /// characters. Content is not preserved. - /// - /// - /// The offset into at which to begin writing the s to decode. - /// - /// The number of characters in to decode. - /// The base64url-decoded form of the . - /// - /// The input must not contain any whitespace or padding characters. - /// Throws if the input is malformed. - /// - public static byte[] Base64UrlDecode(string input, int offset, char[] buffer, int bufferOffset, int count) - { - if (input == null) - { - throw new ArgumentNullException(nameof(input)); - } - if (buffer == null) - { - throw new ArgumentNullException(nameof(buffer)); - } - - ValidateParameters(input.Length, nameof(input), offset, count); - if (bufferOffset < 0) - { - throw new ArgumentOutOfRangeException(nameof(bufferOffset)); - } - - if (count == 0) - { - return EmptyBytes; - } - - // Assumption: input is base64url encoded without padding and contains no whitespace. - - var paddingCharsToAdd = GetNumBase64PaddingCharsToAddForDecode(count); - var arraySizeRequired = checked(count + paddingCharsToAdd); - Debug.Assert(arraySizeRequired % 4 == 0, "Invariant: Array length must be a multiple of 4."); - - if (buffer.Length - bufferOffset < arraySizeRequired) - { - throw new ArgumentException( - string.Format( - CultureInfo.CurrentCulture, - EncoderResources.WebEncoders_InvalidCountOffsetOrLength, - nameof(count), - nameof(bufferOffset), - nameof(input)), - nameof(count)); - } - - // Copy input into buffer, fixing up '-' -> '+' and '_' -> '/'. - var i = bufferOffset; - for (var j = offset; i - bufferOffset < count; i++, j++) - { - var ch = input[j]; - if (ch == '-') - { - buffer[i] = '+'; - } - else if (ch == '_') - { - buffer[i] = '/'; - } - else - { - buffer[i] = ch; - } - } - - // Add the padding characters back. - for (; paddingCharsToAdd > 0; i++, paddingCharsToAdd--) - { - buffer[i] = '='; - } - - // Decode. - // If the caller provided invalid base64 chars, they'll be caught here. - return Convert.FromBase64CharArray(buffer, bufferOffset, arraySizeRequired); - } - - /// - /// Gets the minimum char[] size required for decoding of characters - /// with the method. - /// - /// The number of characters to decode. - /// - /// The minimum char[] size required for decoding of characters. - /// - public static int GetArraySizeRequiredToDecode(int count) - { - if (count < 0) - { - throw new ArgumentOutOfRangeException(nameof(count)); - } - - if (count == 0) - { - return 0; - } - - var numPaddingCharsToAdd = GetNumBase64PaddingCharsToAddForDecode(count); - - return checked(count + numPaddingCharsToAdd); - } - - /// - /// Encodes using base64url encoding. - /// - /// The binary input to encode. - /// The base64url-encoded form of . - public static string Base64UrlEncode(byte[] input) - { - if (input == null) - { - throw new ArgumentNullException(nameof(input)); - } - - return Base64UrlEncode(input, offset: 0, count: input.Length); - } - - /// - /// Encodes using base64url encoding. - /// - /// The binary input to encode. - /// The offset into at which to begin encoding. - /// The number of bytes from to encode. - /// The base64url-encoded form of . - public static string Base64UrlEncode(byte[] input, int offset, int count) - { - if (input == null) - { - throw new ArgumentNullException(nameof(input)); - } - - ValidateParameters(input.Length, nameof(input), offset, count); - - // Special-case empty input - if (count == 0) - { - return string.Empty; - } - - var buffer = new char[GetArraySizeRequiredToEncode(count)]; - var numBase64Chars = Base64UrlEncode(input, offset, buffer, outputOffset: 0, count: count); - - return new String(buffer, startIndex: 0, length: numBase64Chars); - } - - /// - /// Encodes using base64url encoding. - /// - /// The binary input to encode. - /// The offset into at which to begin encoding. - /// - /// Buffer to receive the base64url-encoded form of . Array must be large enough to - /// hold characters and the full base64-encoded form of - /// , including padding characters. - /// - /// - /// The offset into at which to begin writing the base64url-encoded form of - /// . - /// - /// The number of bytes from to encode. - /// - /// The number of characters written to , less any padding characters. - /// - public static int Base64UrlEncode(byte[] input, int offset, char[] output, int outputOffset, int count) - { - if (input == null) - { - throw new ArgumentNullException(nameof(input)); - } - if (output == null) - { - throw new ArgumentNullException(nameof(output)); - } - - ValidateParameters(input.Length, nameof(input), offset, count); - if (outputOffset < 0) - { - throw new ArgumentOutOfRangeException(nameof(outputOffset)); - } - - var arraySizeRequired = GetArraySizeRequiredToEncode(count); - if (output.Length - outputOffset < arraySizeRequired) - { - throw new ArgumentException( - string.Format( - CultureInfo.CurrentCulture, - EncoderResources.WebEncoders_InvalidCountOffsetOrLength, - nameof(count), - nameof(outputOffset), - nameof(output)), - nameof(count)); - } - - // Special-case empty input. - if (count == 0) - { - return 0; - } - - // Use base64url encoding with no padding characters. See RFC 4648, Sec. 5. - - // Start with default Base64 encoding. - var numBase64Chars = Convert.ToBase64CharArray(input, offset, count, output, outputOffset); - - // Fix up '+' -> '-' and '/' -> '_'. Drop padding characters. - for (var i = outputOffset; i - outputOffset < numBase64Chars; i++) - { - var ch = output[i]; - if (ch == '+') - { - output[i] = '-'; - } - else if (ch == '/') - { - output[i] = '_'; - } - else if (ch == '=') - { - // We've reached a padding character; truncate the remainder. - return i - outputOffset; - } - } - - return numBase64Chars; - } - - /// - /// Get the minimum output char[] size required for encoding - /// s with the method. - /// - /// The number of characters to encode. - /// - /// The minimum output char[] size required for encoding s. - /// - public static int GetArraySizeRequiredToEncode(int count) - { - var numWholeOrPartialInputBlocks = checked(count + 2) / 3; - return checked(numWholeOrPartialInputBlocks * 4); - } - - private static int GetNumBase64PaddingCharsInString(string str) - { - // Assumption: input contains a well-formed base64 string with no whitespace. - - // base64 guaranteed have 0 - 2 padding characters. - if (str[str.Length - 1] == '=') - { - if (str[str.Length - 2] == '=') - { - return 2; - } - return 1; - } - return 0; - } - - private static int GetNumBase64PaddingCharsToAddForDecode(int inputLength) - { - switch (inputLength % 4) - { - case 0: - return 0; - case 2: - return 2; - case 3: - return 1; - default: - throw new FormatException( - string.Format( - CultureInfo.CurrentCulture, - EncoderResources.WebEncoders_MalformedInput, - inputLength)); - } - } - - private static void ValidateParameters(int bufferLength, string inputName, int offset, int count) - { - if (offset < 0) - { - throw new ArgumentOutOfRangeException(nameof(offset)); - } - if (count < 0) - { - throw new ArgumentOutOfRangeException(nameof(count)); - } - if (bufferLength - offset < count) - { - throw new ArgumentException( - string.Format( - CultureInfo.CurrentCulture, - EncoderResources.WebEncoders_InvalidCountOffsetOrLength, - nameof(count), - nameof(offset), - inputName), - nameof(count)); - } - } - } -} diff --git a/src/Shared/test/Shared.Tests/WebEncodersTests.cs b/src/Shared/test/Shared.Tests/WebEncodersTests.cs deleted file mode 100644 index 5c71403fd6..0000000000 --- a/src/Shared/test/Shared.Tests/WebEncodersTests.cs +++ /dev/null @@ -1,113 +0,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.Linq; -using Xunit; - -namespace Microsoft.Extensions.Internal -{ - public class WebEncodersTests - { - [Theory] - [InlineData("", 1, 0)] - [InlineData("", 0, 1)] - [InlineData("0123456789", 9, 2)] - [InlineData("0123456789", Int32.MaxValue, 2)] - [InlineData("0123456789", 9, -1)] - public void Base64UrlDecode_BadOffsets(string input, int offset, int count) - { - // Act & assert - Assert.ThrowsAny(() => - { - var retVal = WebEncoders.Base64UrlDecode(input, offset, count); - }); - } - - [Theory] - [InlineData("x")] - [InlineData("(x)")] - public void Base64UrlDecode_MalformedInput(string input) - { - // Act & assert - Assert.Throws(() => - { - var retVal = WebEncoders.Base64UrlDecode(input); - }); - } - - [Theory] - [InlineData("", "")] - [InlineData("123456qwerty++//X+/x", "123456qwerty--__X-_x")] - [InlineData("123456qwerty++//X+/xxw==", "123456qwerty--__X-_xxw")] - [InlineData("123456qwerty++//X+/xxw0=", "123456qwerty--__X-_xxw0")] - public void Base64UrlEncode_And_Decode(string base64Input, string expectedBase64Url) - { - // Arrange - byte[] input = new byte[3].Concat(Convert.FromBase64String(base64Input)).Concat(new byte[2]).ToArray(); - - // Act & assert - 1 - string actualBase64Url = WebEncoders.Base64UrlEncode(input, 3, input.Length - 5); // also helps test offsets - Assert.Equal(expectedBase64Url, actualBase64Url); - - // Act & assert - 2 - // Verify that values round-trip - byte[] roundTripped = WebEncoders.Base64UrlDecode("xx" + actualBase64Url + "yyy", 2, actualBase64Url.Length); // also helps test offsets - string roundTrippedAsBase64 = Convert.ToBase64String(roundTripped); - Assert.Equal(roundTrippedAsBase64, base64Input); - } - - [Theory] - [InlineData("", "")] - [InlineData("123456qwerty++//X+/x", "123456qwerty--__X-_x")] - [InlineData("123456qwerty++//X+/xxw==", "123456qwerty--__X-_xxw")] - [InlineData("123456qwerty++//X+/xxw0=", "123456qwerty--__X-_xxw0")] - public void Base64UrlEncode_And_Decode_WithBufferOffsets(string base64Input, string expectedBase64Url) - { - // Arrange - var input = new byte[3].Concat(Convert.FromBase64String(base64Input)).Concat(new byte[2]).ToArray(); - var buffer = new char[30]; - var output = new char[30]; - for (var i = 0; i < buffer.Length; i++) - { - buffer[i] = '^'; - output[i] = '^'; - } - - // Act 1 - var numEncodedChars = - WebEncoders.Base64UrlEncode(input, offset: 3, output: output, outputOffset: 4, count: input.Length - 5); - - // Assert 1 - var encodedString = new string(output, startIndex: 4, length: numEncodedChars); - Assert.Equal(expectedBase64Url, encodedString); - - // Act 2 - var roundTripInput = new string(output); - var roundTripped = - WebEncoders.Base64UrlDecode(roundTripInput, offset: 4, buffer: buffer, bufferOffset: 5, count: numEncodedChars); - - // Assert 2, verify that values round-trip - var roundTrippedAsBase64 = Convert.ToBase64String(roundTripped); - Assert.Equal(roundTrippedAsBase64, base64Input); - } - - [Theory] - [InlineData(0, 1, 0)] - [InlineData(0, 0, 1)] - [InlineData(10, 9, 2)] - [InlineData(10, Int32.MaxValue, 2)] - [InlineData(10, 9, -1)] - public void Base64UrlEncode_BadOffsets(int inputLength, int offset, int count) - { - // Arrange - byte[] input = new byte[inputLength]; - - // Act & assert - Assert.ThrowsAny(() => - { - var retVal = WebEncoders.Base64UrlEncode(input, offset, count); - }); - } - } -} From 688914bb70063b97dcb2facdbc1ab91980406ece Mon Sep 17 00:00:00 2001 From: Pavel Krymets Date: Mon, 17 Dec 2018 09:18:26 -0800 Subject: [PATCH 0037/1101] Capture LoggedTest.Initialize exception and re-trow in Dispose (#770) --- src/Testing/src/LoggedTest/LoggedTestBase.cs | 64 ++++++++++++-------- 1 file changed, 39 insertions(+), 25 deletions(-) diff --git a/src/Testing/src/LoggedTest/LoggedTestBase.cs b/src/Testing/src/LoggedTest/LoggedTestBase.cs index c3a4e78931..72de6a87c3 100644 --- a/src/Testing/src/LoggedTest/LoggedTestBase.cs +++ b/src/Testing/src/LoggedTest/LoggedTestBase.cs @@ -5,6 +5,7 @@ using System; using System.Linq; using System.Reflection; using System.Runtime.CompilerServices; +using System.Runtime.ExceptionServices; using Microsoft.Extensions.DependencyInjection; using Xunit.Abstractions; @@ -12,6 +13,8 @@ namespace Microsoft.Extensions.Logging.Testing { public class LoggedTestBase : ILoggedTest { + private ExceptionDispatchInfo _initializationException; + private IDisposable _testLog; // Obsolete but keeping for back compat @@ -48,37 +51,48 @@ namespace Microsoft.Extensions.Logging.Testing public virtual void Initialize(MethodInfo methodInfo, object[] testMethodArguments, ITestOutputHelper testOutputHelper) { - TestOutputHelper = testOutputHelper; + try + { + TestOutputHelper = testOutputHelper; - var classType = GetType(); - var logLevelAttribute = methodInfo.GetCustomAttribute() - ?? methodInfo.DeclaringType.GetCustomAttribute() - ?? methodInfo.DeclaringType.Assembly.GetCustomAttribute(); - var testName = testMethodArguments.Aggregate(methodInfo.Name, (a, b) => $"{a}-{(b ?? "null")}"); + var classType = GetType(); + var logLevelAttribute = methodInfo.GetCustomAttribute() + ?? methodInfo.DeclaringType.GetCustomAttribute() + ?? methodInfo.DeclaringType.Assembly.GetCustomAttribute(); + var testName = testMethodArguments.Aggregate(methodInfo.Name, (a, b) => $"{a}-{(b ?? "null")}"); - var useShortClassName = methodInfo.DeclaringType.GetCustomAttribute() - ?? methodInfo.DeclaringType.Assembly.GetCustomAttribute(); - // internal for testing - ResolvedTestClassName = useShortClassName == null ? classType.FullName : classType.Name; + var useShortClassName = methodInfo.DeclaringType.GetCustomAttribute() + ?? methodInfo.DeclaringType.Assembly.GetCustomAttribute(); + // internal for testing + ResolvedTestClassName = useShortClassName == null ? classType.FullName : classType.Name; - _testLog = AssemblyTestLog - .ForAssembly(classType.GetTypeInfo().Assembly) - .StartTestLog( - TestOutputHelper, - ResolvedTestClassName, - out var loggerFactory, - logLevelAttribute?.LogLevel ?? LogLevel.Debug, - out var resolvedTestName, - out var logOutputDirectory, - testName); + _testLog = AssemblyTestLog + .ForAssembly(classType.GetTypeInfo().Assembly) + .StartTestLog( + TestOutputHelper, + ResolvedTestClassName, + out var loggerFactory, + logLevelAttribute?.LogLevel ?? LogLevel.Debug, + out var resolvedTestName, + out var logOutputDirectory, + testName); - ResolvedLogOutputDirectory = logOutputDirectory; - ResolvedTestMethodName = resolvedTestName; + ResolvedLogOutputDirectory = logOutputDirectory; + ResolvedTestMethodName = resolvedTestName; - LoggerFactory = loggerFactory; - Logger = loggerFactory.CreateLogger(classType); + LoggerFactory = loggerFactory; + Logger = loggerFactory.CreateLogger(classType); + } + catch (Exception e) + { + _initializationException = ExceptionDispatchInfo.Capture(e); + } } - public virtual void Dispose() => _testLog.Dispose(); + public virtual void Dispose() + { + _initializationException?.Throw(); + _testLog.Dispose(); + } } } From 6d45c068073e7826fafe89e4b6d447763e41d14f Mon Sep 17 00:00:00 2001 From: Arthur Vickers Date: Fri, 7 Dec 2018 11:58:10 -0800 Subject: [PATCH 0038/1101] Method to get source for each configuration element Fixes dotnet/extensions#609 by allowing the configuration paths to be associated with values to help with debugging. \n\nCommit migrated from https://github.com/dotnet/extensions/commit/d7f8e253d414ce6053ad59b6f974621d5620c0da --- .../src/KeyPerFileConfigurationProvider.cs | 16 +- .../test/ConfigurationProviderTestBase.cs | 325 ++++++++++++++---- .../test/KeyPerFileTests.cs | 81 +---- 3 files changed, 295 insertions(+), 127 deletions(-) diff --git a/src/Configuration.KeyPerFile/src/KeyPerFileConfigurationProvider.cs b/src/Configuration.KeyPerFile/src/KeyPerFileConfigurationProvider.cs index 4748895744..13541110e6 100644 --- a/src/Configuration.KeyPerFile/src/KeyPerFileConfigurationProvider.cs +++ b/src/Configuration.KeyPerFile/src/KeyPerFileConfigurationProvider.cs @@ -39,10 +39,8 @@ namespace Microsoft.Extensions.Configuration.KeyPerFile { return; } - else - { - throw new DirectoryNotFoundException("A non-null file provider for the directory is required when this source is not optional."); - } + + throw new DirectoryNotFoundException("A non-null file provider for the directory is required when this source is not optional."); } var directory = Source.FileProvider.GetDirectoryContents("/"); @@ -68,5 +66,15 @@ namespace Microsoft.Extensions.Configuration.KeyPerFile } } } + + private string GetDirectoryName() + => Source.FileProvider?.GetFileInfo("/")?.PhysicalPath ?? ""; + + /// + /// Generates a string representing this provider name and relevant details. + /// + /// The configuration name. + public override string ToString() + => $"{GetType().Name} for files in '{GetDirectoryName()}' ({(Source.Optional ? "Optional" : "Required")})"; } } diff --git a/src/Configuration.KeyPerFile/test/ConfigurationProviderTestBase.cs b/src/Configuration.KeyPerFile/test/ConfigurationProviderTestBase.cs index c18bea8d28..4609ee2560 100644 --- a/src/Configuration.KeyPerFile/test/ConfigurationProviderTestBase.cs +++ b/src/Configuration.KeyPerFile/test/ConfigurationProviderTestBase.cs @@ -14,7 +14,39 @@ namespace Microsoft.Extensions.Configuration.Test [Fact] public virtual void Load_from_single_provider() { - AssertConfig(BuildConfigRoot(LoadThroughProvider(TestSection.TestConfig))); + var configRoot = BuildConfigRoot(LoadThroughProvider(TestSection.TestConfig)); + + AssertConfig(configRoot); + } + + [Fact] + public virtual void Has_debug_view() + { + var configRoot = BuildConfigRoot(LoadThroughProvider(TestSection.TestConfig)); + var providerTag = configRoot.Providers.Single().ToString(); + + var expected = + $@"Key1=Value1 ({providerTag}) +Section1: + Key2=Value12 ({providerTag}) + Section2: + Key3=Value123 ({providerTag}) + Key3a: + 0=ArrayValue0 ({providerTag}) + 1=ArrayValue1 ({providerTag}) + 2=ArrayValue2 ({providerTag}) +Section3: + Section4: + Key4=Value344 ({providerTag}) +"; + + AssertDebugView(configRoot, expected); + } + + [Fact] + public virtual void Null_values_are_included_in_the_config() + { + AssertConfig(BuildConfigRoot(LoadThroughProvider(TestSection.NullsTestConfig)), expectNulls: true, nullValue: ""); } [Fact] @@ -22,8 +54,13 @@ namespace Microsoft.Extensions.Configuration.Test { AssertConfig( BuildConfigRoot( - LoadUsingMemoryProvider(TestSection.MissingSection2Config), + LoadUsingMemoryProvider(TestSection.MissingSection2ValuesConfig), LoadThroughProvider(TestSection.MissingSection4Config))); + + AssertConfig( + BuildConfigRoot( + LoadUsingMemoryProvider(TestSection.MissingSection4Config), + LoadThroughProvider(TestSection.MissingSection2ValuesConfig))); } [Fact] @@ -31,8 +68,13 @@ namespace Microsoft.Extensions.Configuration.Test { AssertConfig( BuildConfigRoot( - LoadThroughProvider(TestSection.MissingSection2Config), + LoadThroughProvider(TestSection.MissingSection2ValuesConfig), LoadUsingMemoryProvider(TestSection.MissingSection4Config))); + + AssertConfig( + BuildConfigRoot( + LoadThroughProvider(TestSection.MissingSection4Config), + LoadUsingMemoryProvider(TestSection.MissingSection2ValuesConfig))); } [Fact] @@ -128,48 +170,83 @@ namespace Microsoft.Extensions.Configuration.Test public string Key4 { get; set; } } - protected virtual void AssertConfig(IConfigurationRoot config) + protected virtual void AssertDebugView( + IConfigurationRoot config, + string expected) { - Assert.Equal("Value1", config["Key1"], StringComparer.InvariantCultureIgnoreCase); - Assert.Equal("Value12", config["Section1:Key2"], StringComparer.InvariantCultureIgnoreCase); - Assert.Equal("Value123", config["Section1:Section2:Key3"], StringComparer.InvariantCultureIgnoreCase); - Assert.Equal("ArrayValue0", config["Section1:Section2:Key3a:0"], StringComparer.InvariantCultureIgnoreCase); - Assert.Equal("ArrayValue1", config["Section1:Section2:Key3a:1"], StringComparer.InvariantCultureIgnoreCase); - Assert.Equal("ArrayValue2", config["Section1:Section2:Key3a:2"], StringComparer.InvariantCultureIgnoreCase); - Assert.Equal("Value344", config["Section3:Section4:Key4"], StringComparer.InvariantCultureIgnoreCase); + string RemoveLineEnds(string source) => source.Replace("\n", "").Replace("\r", ""); + + var actual = config.GetDebugView(); + + Assert.Equal( + RemoveLineEnds(expected), + RemoveLineEnds(actual)); + } + + protected virtual void AssertConfig( + IConfigurationRoot config, + bool expectNulls = false, + string nullValue = null) + { + var value1 = expectNulls ? nullValue : "Value1"; + var value12 = expectNulls ? nullValue : "Value12"; + var value123 = expectNulls ? nullValue : "Value123"; + var arrayvalue0 = expectNulls ? nullValue : "ArrayValue0"; + var arrayvalue1 = expectNulls ? nullValue : "ArrayValue1"; + var arrayvalue2 = expectNulls ? nullValue : "ArrayValue2"; + var value344 = expectNulls ? nullValue : "Value344"; + + Assert.Equal(value1, config["Key1"], StringComparer.InvariantCultureIgnoreCase); + Assert.Equal(value12, config["Section1:Key2"], StringComparer.InvariantCultureIgnoreCase); + Assert.Equal(value123, config["Section1:Section2:Key3"], StringComparer.InvariantCultureIgnoreCase); + Assert.Equal(arrayvalue0, config["Section1:Section2:Key3a:0"], StringComparer.InvariantCultureIgnoreCase); + Assert.Equal(arrayvalue1, config["Section1:Section2:Key3a:1"], StringComparer.InvariantCultureIgnoreCase); + Assert.Equal(arrayvalue2, config["Section1:Section2:Key3a:2"], StringComparer.InvariantCultureIgnoreCase); + Assert.Equal(value344, config["Section3:Section4:Key4"], StringComparer.InvariantCultureIgnoreCase); var section1 = config.GetSection("Section1"); - Assert.Equal("Value12", section1["Key2"], StringComparer.InvariantCultureIgnoreCase); - Assert.Equal("Value123", section1["Section2:Key3"], StringComparer.InvariantCultureIgnoreCase); - Assert.Equal("ArrayValue0", section1["Section2:Key3a:0"], StringComparer.InvariantCultureIgnoreCase); - Assert.Equal("ArrayValue1", section1["Section2:Key3a:1"], StringComparer.InvariantCultureIgnoreCase); - Assert.Equal("ArrayValue2", section1["Section2:Key3a:2"], StringComparer.InvariantCultureIgnoreCase); + Assert.Equal(value12, section1["Key2"], StringComparer.InvariantCultureIgnoreCase); + Assert.Equal(value123, section1["Section2:Key3"], StringComparer.InvariantCultureIgnoreCase); + Assert.Equal(arrayvalue0, section1["Section2:Key3a:0"], StringComparer.InvariantCultureIgnoreCase); + Assert.Equal(arrayvalue1, section1["Section2:Key3a:1"], StringComparer.InvariantCultureIgnoreCase); + Assert.Equal(arrayvalue2, section1["Section2:Key3a:2"], StringComparer.InvariantCultureIgnoreCase); Assert.Equal("Section1", section1.Path, StringComparer.InvariantCultureIgnoreCase); Assert.Null(section1.Value); var section2 = config.GetSection("Section1:Section2"); - Assert.Equal("Value123", section2["Key3"], StringComparer.InvariantCultureIgnoreCase); - Assert.Equal("ArrayValue0", section2["Key3a:0"], StringComparer.InvariantCultureIgnoreCase); - Assert.Equal("ArrayValue1", section2["Key3a:1"], StringComparer.InvariantCultureIgnoreCase); - Assert.Equal("ArrayValue2", section2["Key3a:2"], StringComparer.InvariantCultureIgnoreCase); + Assert.Equal(value123, section2["Key3"], StringComparer.InvariantCultureIgnoreCase); + Assert.Equal(arrayvalue0, section2["Key3a:0"], StringComparer.InvariantCultureIgnoreCase); + Assert.Equal(arrayvalue1, section2["Key3a:1"], StringComparer.InvariantCultureIgnoreCase); + Assert.Equal(arrayvalue2, section2["Key3a:2"], StringComparer.InvariantCultureIgnoreCase); Assert.Equal("Section1:Section2", section2.Path, StringComparer.InvariantCultureIgnoreCase); Assert.Null(section2.Value); section2 = section1.GetSection("Section2"); - Assert.Equal("Value123", section2["Key3"], StringComparer.InvariantCultureIgnoreCase); - Assert.Equal("ArrayValue0", section2["Key3a:0"], StringComparer.InvariantCultureIgnoreCase); - Assert.Equal("ArrayValue1", section2["Key3a:1"], StringComparer.InvariantCultureIgnoreCase); - Assert.Equal("ArrayValue2", section2["Key3a:2"], StringComparer.InvariantCultureIgnoreCase); + Assert.Equal(value123, section2["Key3"], StringComparer.InvariantCultureIgnoreCase); + Assert.Equal(arrayvalue0, section2["Key3a:0"], StringComparer.InvariantCultureIgnoreCase); + Assert.Equal(arrayvalue1, section2["Key3a:1"], StringComparer.InvariantCultureIgnoreCase); + Assert.Equal(arrayvalue2, section2["Key3a:2"], StringComparer.InvariantCultureIgnoreCase); Assert.Equal("Section1:Section2", section2.Path, StringComparer.InvariantCultureIgnoreCase); Assert.Null(section2.Value); + var section3a = section2.GetSection("Key3a"); + Assert.Equal(arrayvalue0, section3a["0"], StringComparer.InvariantCultureIgnoreCase); + Assert.Equal(arrayvalue1, section3a["1"], StringComparer.InvariantCultureIgnoreCase); + Assert.Equal(arrayvalue2, section3a["2"], StringComparer.InvariantCultureIgnoreCase); + Assert.Equal("Section1:Section2:Key3a", section3a.Path, StringComparer.InvariantCultureIgnoreCase); + Assert.Null(section3a.Value); + + var section3 = config.GetSection("Section3"); + Assert.Equal("Section3", section3.Path, StringComparer.InvariantCultureIgnoreCase); + Assert.Null(section3.Value); + var section4 = config.GetSection("Section3:Section4"); - Assert.Equal("Value344", section4["Key4"], StringComparer.InvariantCultureIgnoreCase); + Assert.Equal(value344, section4["Key4"], StringComparer.InvariantCultureIgnoreCase); Assert.Equal("Section3:Section4", section4.Path, StringComparer.InvariantCultureIgnoreCase); Assert.Null(section4.Value); section4 = config.GetSection("Section3").GetSection("Section4"); - Assert.Equal("Value344", section4["Key4"], StringComparer.InvariantCultureIgnoreCase); + Assert.Equal(value344, section4["Key4"], StringComparer.InvariantCultureIgnoreCase); Assert.Equal("Section3:Section4", section4.Path, StringComparer.InvariantCultureIgnoreCase); Assert.Null(section4.Value); @@ -179,7 +256,7 @@ namespace Microsoft.Extensions.Configuration.Test Assert.Equal("Key1", sections[0].Key, StringComparer.InvariantCultureIgnoreCase); Assert.Equal("Key1", sections[0].Path, StringComparer.InvariantCultureIgnoreCase); - Assert.Equal("Value1", sections[0].Value, StringComparer.InvariantCultureIgnoreCase); + Assert.Equal(value1, sections[0].Value, StringComparer.InvariantCultureIgnoreCase); Assert.Equal("Section1", sections[1].Key, StringComparer.InvariantCultureIgnoreCase); Assert.Equal("Section1", sections[1].Path, StringComparer.InvariantCultureIgnoreCase); @@ -195,11 +272,55 @@ namespace Microsoft.Extensions.Configuration.Test Assert.Equal("Key2", sections[0].Key, StringComparer.InvariantCultureIgnoreCase); Assert.Equal("Section1:Key2", sections[0].Path, StringComparer.InvariantCultureIgnoreCase); - Assert.Equal("Value12", sections[0].Value, StringComparer.InvariantCultureIgnoreCase); + Assert.Equal(value12, sections[0].Value, StringComparer.InvariantCultureIgnoreCase); Assert.Equal("Section2", sections[1].Key, StringComparer.InvariantCultureIgnoreCase); Assert.Equal("Section1:Section2", sections[1].Path, StringComparer.InvariantCultureIgnoreCase); Assert.Null(sections[1].Value); + + sections = section2.GetChildren().ToList(); + + Assert.Equal(2, sections.Count); + + Assert.Equal("Key3", sections[0].Key, StringComparer.InvariantCultureIgnoreCase); + Assert.Equal("Section1:Section2:Key3", sections[0].Path, StringComparer.InvariantCultureIgnoreCase); + Assert.Equal(value123, sections[0].Value, StringComparer.InvariantCultureIgnoreCase); + + Assert.Equal("Key3a", sections[1].Key, StringComparer.InvariantCultureIgnoreCase); + Assert.Equal("Section1:Section2:Key3a", sections[1].Path, StringComparer.InvariantCultureIgnoreCase); + Assert.Null(sections[1].Value); + + sections = section3a.GetChildren().ToList(); + + Assert.Equal(3, sections.Count); + + Assert.Equal("0", sections[0].Key, StringComparer.InvariantCultureIgnoreCase); + Assert.Equal("Section1:Section2:Key3a:0", sections[0].Path, StringComparer.InvariantCultureIgnoreCase); + Assert.Equal(arrayvalue0, sections[0].Value, StringComparer.InvariantCultureIgnoreCase); + + Assert.Equal("1", sections[1].Key, StringComparer.InvariantCultureIgnoreCase); + Assert.Equal("Section1:Section2:Key3a:1", sections[1].Path, StringComparer.InvariantCultureIgnoreCase); + Assert.Equal(arrayvalue1, sections[1].Value, StringComparer.InvariantCultureIgnoreCase); + + Assert.Equal("2", sections[2].Key, StringComparer.InvariantCultureIgnoreCase); + Assert.Equal("Section1:Section2:Key3a:2", sections[2].Path, StringComparer.InvariantCultureIgnoreCase); + Assert.Equal(arrayvalue2, sections[2].Value, StringComparer.InvariantCultureIgnoreCase); + + sections = section3.GetChildren().ToList(); + + Assert.Single(sections); + + Assert.Equal("Section4", sections[0].Key, StringComparer.InvariantCultureIgnoreCase); + Assert.Equal("Section3:Section4", sections[0].Path, StringComparer.InvariantCultureIgnoreCase); + Assert.Null(sections[0].Value); + + sections = section4.GetChildren().ToList(); + + Assert.Single(sections); + + Assert.Equal("Key4", sections[0].Key, StringComparer.InvariantCultureIgnoreCase); + Assert.Equal("Section3:Section4:Key4", sections[0].Path, StringComparer.InvariantCultureIgnoreCase); + Assert.Equal(value344, sections[0].Value, StringComparer.InvariantCultureIgnoreCase); } protected abstract (IConfigurationProvider Provider, Action Initializer) LoadThroughProvider(TestSection testConfig); @@ -272,7 +393,7 @@ namespace Microsoft.Extensions.Configuration.Test public IEnumerable<(string Key, string Value)> Expand(string key) { - if (AsString != null) + if (AsArray == null) { yield return (key, AsString); } @@ -310,7 +431,7 @@ namespace Microsoft.Extensions.Configuration.Test Values = new[] { ("Key3", (TestKeyValue)"Value123"), - ("Key3a", (TestKeyValue)new[] {"ArrayValue0", "ArrayValue1", "ArrayValue2"}), + ("Key3a", (TestKeyValue)new[] {"ArrayValue0", "ArrayValue1", "ArrayValue2"}) }, }) } @@ -321,11 +442,10 @@ namespace Microsoft.Extensions.Configuration.Test { ("Section4", new TestSection { - Values = new[] {("Key4", (TestKeyValue)"Value344")}, + Values = new[] {("Key4", (TestKeyValue)"Value344")} }) } - }), - + }) } }; @@ -345,7 +465,7 @@ namespace Microsoft.Extensions.Configuration.Test Values = new[] { ("Key3", (TestKeyValue)"-----"), - ("Key3a", (TestKeyValue)new[] {"-----------", "-----------", "-----------"}), + ("Key3a", (TestKeyValue)new[] {"-----------", "-----------", "-----------"}) }, }) } @@ -356,15 +476,14 @@ namespace Microsoft.Extensions.Configuration.Test { ("Section4", new TestSection { - Values = new[] {("Key4", (TestKeyValue)"--------")}, + Values = new[] {("Key4", (TestKeyValue)"--------")} }) } - }), - + }) } }; - public static TestSection MissingSection2Config { get; } + public static TestSection MissingSection2ValuesConfig { get; } = new TestSection { Values = new[] { ("Key1", (TestKeyValue)"Value1") }, @@ -373,6 +492,16 @@ namespace Microsoft.Extensions.Configuration.Test ("Section1", new TestSection { Values = new[] {("Key2", (TestKeyValue)"Value12")}, + Sections = new[] + { + ("Section2", new TestSection + { + Values = new[] + { + ("Key3a", (TestKeyValue)new[] {"ArrayValue0"}) + }, + }) + } }), ("Section3", new TestSection { @@ -380,11 +509,10 @@ namespace Microsoft.Extensions.Configuration.Test { ("Section4", new TestSection { - Values = new[] {("Key4", (TestKeyValue)"Value344")}, + Values = new[] {("Key4", (TestKeyValue)"Value344")} }) } - }), - + }) } }; @@ -405,15 +533,12 @@ namespace Microsoft.Extensions.Configuration.Test Values = new[] { ("Key3", (TestKeyValue)"Value123"), - ("Key3a", (TestKeyValue)new[] {"ArrayValue0", "ArrayValue1", "ArrayValue2"}), + ("Key3a", (TestKeyValue)new[] {"ArrayValue0", "ArrayValue1", "ArrayValue2"}) }, }) } }), - ("Section3", new TestSection - { - }), - + ("Section3", new TestSection()) } }; @@ -433,7 +558,7 @@ namespace Microsoft.Extensions.Configuration.Test Values = new[] { ("KeY3", (TestKeyValue)"Value123"), - ("KeY3a", (TestKeyValue)new[] {"ArrayValue0", "ArrayValue1", "ArrayValue2"}), + ("KeY3a", (TestKeyValue)new[] {"ArrayValue0", "ArrayValue1", "ArrayValue2"}) }, }) } @@ -444,11 +569,10 @@ namespace Microsoft.Extensions.Configuration.Test { ("SectioN4", new TestSection { - Values = new[] {("KeY4", (TestKeyValue)"Value344")}, + Values = new[] {("KeY4", (TestKeyValue)"Value344")} }) } - }), - + }) } }; @@ -472,7 +596,7 @@ namespace Microsoft.Extensions.Configuration.Test Values = new[] { ("Key3", (TestKeyValue)"Value123"), - ("Key3a", (TestKeyValue)new[] {"ArrayValue0", "ArrayValue1", "ArrayValue2"}), + ("Key3a", (TestKeyValue)new[] {"ArrayValue0", "ArrayValue1", "ArrayValue2"}) }, }), ("Section2", new TestSection @@ -480,7 +604,7 @@ namespace Microsoft.Extensions.Configuration.Test Values = new[] { ("Key3", (TestKeyValue)"Value123"), - ("Key3a", (TestKeyValue)new[] {"ArrayValue0", "ArrayValue1", "ArrayValue2"}), + ("Key3a", (TestKeyValue)new[] {"ArrayValue0", "ArrayValue1", "ArrayValue2"}) }, }) @@ -492,11 +616,10 @@ namespace Microsoft.Extensions.Configuration.Test { ("Section4", new TestSection { - Values = new[] {("Key4", (TestKeyValue)"Value344")}, + Values = new[] {("Key4", (TestKeyValue)"Value344")} }) } - }), - + }) } }; @@ -520,7 +643,7 @@ namespace Microsoft.Extensions.Configuration.Test Values = new[] { ("Key3", (TestKeyValue)"Value123"), - ("Key3a", (TestKeyValue)new[] {"ArrayValue0", "ArrayValue1", "ArrayValue2"}), + ("Key3a", (TestKeyValue)new[] {"ArrayValue0", "ArrayValue1", "ArrayValue2"}) }, }), ("SectioN2", new TestSection @@ -528,7 +651,7 @@ namespace Microsoft.Extensions.Configuration.Test Values = new[] { ("KeY3", (TestKeyValue)"Value123"), - ("KeY3a", (TestKeyValue)new[] {"ArrayValue0", "ArrayValue1", "ArrayValue2"}), + ("KeY3a", (TestKeyValue)new[] {"ArrayValue0", "ArrayValue1", "ArrayValue2"}) }, }) @@ -540,11 +663,97 @@ namespace Microsoft.Extensions.Configuration.Test { ("Section4", new TestSection { - Values = new[] {("Key4", (TestKeyValue)"Value344")}, + Values = new[] {("Key4", (TestKeyValue)"Value344")} + }) + } + }) + } + }; + + public static TestSection NullsTestConfig { get; } + = new TestSection + { + Values = new[] { ("Key1", new TestKeyValue((string)null)) }, + Sections = new[] + { + ("Section1", new TestSection + { + Values = new[] {("Key2", new TestKeyValue((string)null))}, + Sections = new[] + { + ("Section2", new TestSection + { + Values = new[] + { + ("Key3", new TestKeyValue((string)null)), + ("Key3a", (TestKeyValue)new string[] {null, null, null}) + }, }) } }), + ("Section3", new TestSection + { + Sections = new[] + { + ("Section4", new TestSection + { + Values = new[] {("Key4", new TestKeyValue((string)null))} + }) + } + }) + } + }; + public static TestSection ExtraValuesTestConfig { get; } + = new TestSection + { + Values = new[] + { + ("Key1", (TestKeyValue)"Value1"), + ("Key1r", (TestKeyValue)"Value1r") + }, + Sections = new[] + { + ("Section1", new TestSection + { + Values = new[] + { + ("Key2", (TestKeyValue)"Value12"), + ("Key2r", (TestKeyValue)"Value12r") + }, + Sections = new[] + { + ("Section2", new TestSection + { + Values = new[] + { + ("Key3", (TestKeyValue)"Value123"), + ("Key3a", (TestKeyValue)new[] {"ArrayValue0", "ArrayValue1", "ArrayValue2", "ArrayValue2r"}), + ("Key3ar", (TestKeyValue)new[] {"ArrayValue0r"}) + }, + }) + } + }), + ("Section3", new TestSection + { + Sections = new[] + { + ("Section4", new TestSection + { + Values = new[] {("Key4", (TestKeyValue)"Value344")} + }) + } + }), + ("Section5r", new TestSection + { + Sections = new[] + { + ("Section6r", new TestSection + { + Values = new[] {("Key5r", (TestKeyValue)"Value565r")} + }) + } + }) } }; } diff --git a/src/Configuration.KeyPerFile/test/KeyPerFileTests.cs b/src/Configuration.KeyPerFile/test/KeyPerFileTests.cs index fac0e839e8..4de528c011 100644 --- a/src/Configuration.KeyPerFile/test/KeyPerFileTests.cs +++ b/src/Configuration.KeyPerFile/test/KeyPerFileTests.cs @@ -190,20 +190,11 @@ namespace Microsoft.Extensions.Configuration.KeyPerFile.Test _contents = new TestDirectoryContents(files); } - public IDirectoryContents GetDirectoryContents(string subpath) - { - return _contents; - } + public IDirectoryContents GetDirectoryContents(string subpath) => _contents; - public IFileInfo GetFileInfo(string subpath) - { - throw new NotImplementedException(); - } + public IFileInfo GetFileInfo(string subpath) => new TestFile("TestDirectory"); - public IChangeToken Watch(string filter) - { - throw new NotImplementedException(); - } + public IChangeToken Watch(string filter) => throw new NotImplementedException(); } class TestDirectoryContents : IDirectoryContents @@ -215,75 +206,33 @@ namespace Microsoft.Extensions.Configuration.KeyPerFile.Test _list = new List(files); } - public bool Exists - { - get - { - return true; - } - } + public bool Exists => true; - public IEnumerator GetEnumerator() - { - return _list.GetEnumerator(); - } + public IEnumerator GetEnumerator() => _list.GetEnumerator(); - IEnumerator IEnumerable.GetEnumerator() - { - return GetEnumerator(); - } + IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); } //TODO: Probably need a directory and file type. class TestFile : IFileInfo { - private string _name; - private string _contents; + private readonly string _name; + private readonly string _contents; - public bool Exists - { - get - { - return true; - } - } + public bool Exists => true; public bool IsDirectory { get; } - public DateTimeOffset LastModified - { - get - { - throw new NotImplementedException(); - } - } + public DateTimeOffset LastModified => throw new NotImplementedException(); - public long Length - { - get - { - throw new NotImplementedException(); - } - } + public long Length => throw new NotImplementedException(); - public string Name - { - get - { - return _name; - } - } + public string Name => _name; - public string PhysicalPath - { - get - { - throw new NotImplementedException(); - } - } + public string PhysicalPath => "Root/" + Name; public TestFile(string name) { @@ -304,7 +253,9 @@ namespace Microsoft.Extensions.Configuration.KeyPerFile.Test throw new InvalidOperationException("Cannot create stream from directory"); } - return new MemoryStream(Encoding.UTF8.GetBytes(_contents)); + return _contents == null + ? new MemoryStream() + : new MemoryStream(Encoding.UTF8.GetBytes(_contents)); } } } From 5f7ab102cfe885af5972449a71aeffb4bfdd9946 Mon Sep 17 00:00:00 2001 From: Nate McMaster Date: Wed, 19 Dec 2018 13:43:43 -0800 Subject: [PATCH 0039/1101] Fix dotnet/extensions#815 - fix path to embedded manifest task file \n\nCommit migrated from https://github.com/dotnet/extensions/commit/cab86d2e3601db232849e4fab078ff6d5dc4df8d --- .../src/Microsoft.Extensions.FileProviders.Embedded.nuspec | 4 +--- .../Microsoft.Extensions.FileProviders.Embedded.props | 4 +--- 2 files changed, 2 insertions(+), 6 deletions(-) diff --git a/src/FileProviders/Embedded/src/Microsoft.Extensions.FileProviders.Embedded.nuspec b/src/FileProviders/Embedded/src/Microsoft.Extensions.FileProviders.Embedded.nuspec index af7feca8fd..ff6d385add 100644 --- a/src/FileProviders/Embedded/src/Microsoft.Extensions.FileProviders.Embedded.nuspec +++ b/src/FileProviders/Embedded/src/Microsoft.Extensions.FileProviders.Embedded.nuspec @@ -27,7 +27,5 @@ - - - \ No newline at end of file + diff --git a/src/FileProviders/Embedded/src/build/netstandard2.0/Microsoft.Extensions.FileProviders.Embedded.props b/src/FileProviders/Embedded/src/build/netstandard2.0/Microsoft.Extensions.FileProviders.Embedded.props index 66ea7b5c22..aabbabc92f 100644 --- a/src/FileProviders/Embedded/src/build/netstandard2.0/Microsoft.Extensions.FileProviders.Embedded.props +++ b/src/FileProviders/Embedded/src/build/netstandard2.0/Microsoft.Extensions.FileProviders.Embedded.props @@ -5,9 +5,7 @@ - <_FileProviderTaskFolder Condition="'$(MSBuildRuntimeType)' == 'Core'">netstandard2.0 - <_FileProviderTaskFolder Condition="'$(MSBuildRuntimeType)' != 'Core'">net461 - <_FileProviderTaskAssembly>$(MSBuildThisFileDirectory)..\..\tasks\$(_FileProviderTaskFolder)\Microsoft.Extensions.FileProviders.Embedded.Manifest.Task.dll + <_FileProviderTaskAssembly>$(MSBuildThisFileDirectory)..\..\tasks\netstandard2.0\Microsoft.Extensions.FileProviders.Embedded.Manifest.Task.dll Date: Sun, 23 Dec 2018 18:28:10 -0800 Subject: [PATCH 0040/1101] Fix aspnet/Extensionsdotnet/extensions#639 The issue is that the hosted service would not be registered if other hosted services are present. The fix is to use TryAddEnumble so that we allow multipled `IHostedService`s but not multiple instances of the same type. \n\nCommit migrated from https://github.com/dotnet/extensions/commit/a2f28147c21d5272fb66b75a47325cd9e63cde58 --- .../HealthCheckServiceCollectionExtensions.cs | 2 +- .../ServiceCollectionExtensionsTest.cs | 53 +++++++++++++++++++ 2 files changed, 54 insertions(+), 1 deletion(-) diff --git a/src/HealthChecks/HealthChecks/src/DependencyInjection/HealthCheckServiceCollectionExtensions.cs b/src/HealthChecks/HealthChecks/src/DependencyInjection/HealthCheckServiceCollectionExtensions.cs index d6df03d2ae..91ffa59449 100644 --- a/src/HealthChecks/HealthChecks/src/DependencyInjection/HealthCheckServiceCollectionExtensions.cs +++ b/src/HealthChecks/HealthChecks/src/DependencyInjection/HealthCheckServiceCollectionExtensions.cs @@ -26,7 +26,7 @@ namespace Microsoft.Extensions.DependencyInjection public static IHealthChecksBuilder AddHealthChecks(this IServiceCollection services) { services.TryAddSingleton(); - services.TryAddSingleton(); + services.TryAddEnumerable(ServiceDescriptor.Singleton()); return new HealthChecksBuilder(services); } } diff --git a/src/HealthChecks/HealthChecks/test/DependencyInjection/ServiceCollectionExtensionsTest.cs b/src/HealthChecks/HealthChecks/test/DependencyInjection/ServiceCollectionExtensionsTest.cs index 694a97628d..98371e9f13 100644 --- a/src/HealthChecks/HealthChecks/test/DependencyInjection/ServiceCollectionExtensionsTest.cs +++ b/src/HealthChecks/HealthChecks/test/DependencyInjection/ServiceCollectionExtensionsTest.cs @@ -2,6 +2,8 @@ // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System.Linq; +using System.Threading; +using System.Threading.Tasks; using Microsoft.Extensions.Diagnostics.HealthChecks; using Microsoft.Extensions.Hosting; using Xunit; @@ -39,5 +41,56 @@ namespace Microsoft.Extensions.DependencyInjection Assert.Null(actual.ImplementationFactory); }); } + + [Fact] // see: https://github.com/aspnet/Extensions/issues/639 + public void AddHealthChecks_RegistersPublisherService_WhenOtherHostedServicesRegistered() + { + // Arrange + var services = new ServiceCollection(); + + // Act + services.AddSingleton(); + services.AddHealthChecks(); + + // Assert + Assert.Collection(services.OrderBy(s => s.ServiceType.FullName).ThenBy(s => s.ImplementationType.FullName), + actual => + { + Assert.Equal(ServiceLifetime.Singleton, actual.Lifetime); + Assert.Equal(typeof(HealthCheckService), actual.ServiceType); + Assert.Equal(typeof(DefaultHealthCheckService), actual.ImplementationType); + Assert.Null(actual.ImplementationInstance); + Assert.Null(actual.ImplementationFactory); + }, + actual => + { + Assert.Equal(ServiceLifetime.Singleton, actual.Lifetime); + Assert.Equal(typeof(IHostedService), actual.ServiceType); + Assert.Equal(typeof(DummyHostedService), actual.ImplementationType); + Assert.Null(actual.ImplementationInstance); + Assert.Null(actual.ImplementationFactory); + }, + actual => + { + Assert.Equal(ServiceLifetime.Singleton, actual.Lifetime); + Assert.Equal(typeof(IHostedService), actual.ServiceType); + Assert.Equal(typeof(HealthCheckPublisherHostedService), actual.ImplementationType); + Assert.Null(actual.ImplementationInstance); + Assert.Null(actual.ImplementationFactory); + }); + } + + private class DummyHostedService : IHostedService + { + public Task StartAsync(CancellationToken cancellationToken) + { + throw new System.NotImplementedException(); + } + + public Task StopAsync(CancellationToken cancellationToken) + { + throw new System.NotImplementedException(); + } + } } } From bdc2ea81c0da8ca6e0c7633c4b37769de216e995 Mon Sep 17 00:00:00 2001 From: Pavel Krymets Date: Mon, 31 Dec 2018 12:54:54 -0800 Subject: [PATCH 0041/1101] Unify TypeNameHelpers (dotnet/extensions#876) \n\nCommit migrated from https://github.com/dotnet/extensions/commit/2f53c3619543bb2b7a166c1926ea462b8521d2fc --- src/Shared/TypeNameHelper/TypeNameHelper.cs | 59 ++++++++++++------- .../test/Shared.Tests/TypeNameHelperTest.cs | 41 +++++++++++++ 2 files changed, 80 insertions(+), 20 deletions(-) diff --git a/src/Shared/TypeNameHelper/TypeNameHelper.cs b/src/Shared/TypeNameHelper/TypeNameHelper.cs index 1cc7468646..3994a074b6 100644 --- a/src/Shared/TypeNameHelper/TypeNameHelper.cs +++ b/src/Shared/TypeNameHelper/TypeNameHelper.cs @@ -9,6 +9,8 @@ namespace Microsoft.Extensions.Internal { internal class TypeNameHelper { + private const char DefaultNestedTypeDelimiter = '+'; + private static readonly Dictionary _builtInTypeNames = new Dictionary { { typeof(void), "void" }, @@ -40,15 +42,17 @@ namespace Microsoft.Extensions.Internal /// The . /// true to print a fully qualified name. /// true to include generic parameter names. + /// true to include generic parameters. + /// Character to use as a delimiter in nested type names /// The pretty printed type name. - public static string GetTypeDisplayName(Type type, bool fullName = true, bool includeGenericParameterNames = false) + public static string GetTypeDisplayName(Type type, bool fullName = true, bool includeGenericParameterNames = false, bool includeGenericParameters = true, char nestedTypeDelimiter = DefaultNestedTypeDelimiter) { var builder = new StringBuilder(); - ProcessType(builder, type, new DisplayNameOptions(fullName, includeGenericParameterNames)); + ProcessType(builder, type, new DisplayNameOptions(fullName, includeGenericParameterNames, includeGenericParameters, nestedTypeDelimiter)); return builder.ToString(); } - private static void ProcessType(StringBuilder builder, Type type, DisplayNameOptions options) + private static void ProcessType(StringBuilder builder, Type type, in DisplayNameOptions options) { if (type.IsGenericType) { @@ -72,11 +76,17 @@ namespace Microsoft.Extensions.Internal } else { - builder.Append(options.FullName ? type.FullName : type.Name); + var name = options.FullName ? type.FullName : type.Name; + builder.Append(name); + + if (options.NestedTypeDelimiter != DefaultNestedTypeDelimiter) + { + builder.Replace(DefaultNestedTypeDelimiter, options.NestedTypeDelimiter, builder.Length - name.Length, name.Length); + } } } - private static void ProcessArrayType(StringBuilder builder, Type type, DisplayNameOptions options) + private static void ProcessArrayType(StringBuilder builder, Type type, in DisplayNameOptions options) { var innerType = type; while (innerType.IsArray) @@ -95,7 +105,7 @@ namespace Microsoft.Extensions.Internal } } - private static void ProcessGenericType(StringBuilder builder, Type type, Type[] genericArguments, int length, DisplayNameOptions options) + private static void ProcessGenericType(StringBuilder builder, Type type, Type[] genericArguments, int length, in DisplayNameOptions options) { var offset = 0; if (type.IsNested) @@ -108,7 +118,7 @@ namespace Microsoft.Extensions.Internal if (type.IsNested) { ProcessGenericType(builder, type.DeclaringType, genericArguments, offset, options); - builder.Append('+'); + builder.Append(options.NestedTypeDelimiter); } else if (!string.IsNullOrEmpty(type.Namespace)) { @@ -126,35 +136,44 @@ namespace Microsoft.Extensions.Internal builder.Append(type.Name, 0, genericPartIndex); - builder.Append('<'); - for (var i = offset; i < length; i++) + if (options.IncludeGenericParameters) { - ProcessType(builder, genericArguments[i], options); - if (i + 1 == length) + builder.Append('<'); + for (var i = offset; i < length; i++) { - continue; - } + ProcessType(builder, genericArguments[i], options); + if (i + 1 == length) + { + continue; + } - builder.Append(','); - if (options.IncludeGenericParameterNames || !genericArguments[i + 1].IsGenericParameter) - { - builder.Append(' '); + builder.Append(','); + if (options.IncludeGenericParameterNames || !genericArguments[i + 1].IsGenericParameter) + { + builder.Append(' '); + } } + builder.Append('>'); } - builder.Append('>'); } - private struct DisplayNameOptions + private readonly struct DisplayNameOptions { - public DisplayNameOptions(bool fullName, bool includeGenericParameterNames) + public DisplayNameOptions(bool fullName, bool includeGenericParameterNames, bool includeGenericParameters, char nestedTypeDelimiter) { FullName = fullName; + IncludeGenericParameters = includeGenericParameters; IncludeGenericParameterNames = includeGenericParameterNames; + NestedTypeDelimiter = nestedTypeDelimiter; } public bool FullName { get; } + public bool IncludeGenericParameters { get; } + public bool IncludeGenericParameterNames { get; } + + public char NestedTypeDelimiter { get; } } } } diff --git a/src/Shared/test/Shared.Tests/TypeNameHelperTest.cs b/src/Shared/test/Shared.Tests/TypeNameHelperTest.cs index b7f4285bdc..bd29f647d1 100644 --- a/src/Shared/test/Shared.Tests/TypeNameHelperTest.cs +++ b/src/Shared/test/Shared.Tests/TypeNameHelperTest.cs @@ -216,6 +216,47 @@ namespace Microsoft.Extensions.Internal Assert.Equal(expected, actual); } + public static TheoryData FullTypeNameData + { + get + { + return new TheoryData + { + // Predefined Types + { typeof(int), "int" }, + { typeof(List), "System.Collections.Generic.List" }, + { typeof(Dictionary), "System.Collections.Generic.Dictionary" }, + { typeof(Dictionary>), "System.Collections.Generic.Dictionary" }, + { typeof(List>), "System.Collections.Generic.List" }, + + // Classes inside NonGeneric class + { typeof(A), "Microsoft.Extensions.Internal.TypeNameHelperTest.A" }, + { typeof(B), "Microsoft.Extensions.Internal.TypeNameHelperTest.B" }, + { typeof(C), "Microsoft.Extensions.Internal.TypeNameHelperTest.C" }, + { typeof(C>), "Microsoft.Extensions.Internal.TypeNameHelperTest.C" }, + { typeof(B>), "Microsoft.Extensions.Internal.TypeNameHelperTest.B" }, + + // Classes inside Generic class + { typeof(Outer.D), "Microsoft.Extensions.Internal.TypeNameHelperTest.Outer.D" }, + { typeof(Outer.E), "Microsoft.Extensions.Internal.TypeNameHelperTest.Outer.E" }, + { typeof(Outer.F), "Microsoft.Extensions.Internal.TypeNameHelperTest.Outer.F" }, + { typeof(Outer.F.E>),"Microsoft.Extensions.Internal.TypeNameHelperTest.Outer.F" }, + { typeof(Outer.E.E>), "Microsoft.Extensions.Internal.TypeNameHelperTest.Outer.E" } + }; + } + } + + [Theory] + [MemberData(nameof(FullTypeNameData))] + public void Can_PrettyPrint_FullTypeName_WithoutGenericParametersAndNestedTypeDelimiter(Type type, string expectedTypeName) + { + // Arrange & Act + var displayName = TypeNameHelper.GetTypeDisplayName(type, fullName: true, includeGenericParameters: false, nestedTypeDelimiter: '.'); + + // Assert + Assert.Equal(expectedTypeName, displayName); + } + private class A { } private class B { } From 3287fba28f61d9167bcb342a5447aec0ad45cacd Mon Sep 17 00:00:00 2001 From: James Newton-King Date: Mon, 7 Jan 2019 15:37:53 +1300 Subject: [PATCH 0042/1101] Don't timeout in testing task extensions when debugger is attached (dotnet/extensions#903) \n\nCommit migrated from https://github.com/dotnet/extensions/commit/24c84a5b1db0fca3cfcc54eed3f4f94c6670a709 --- src/Testing/src/TaskExtensions.cs | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/src/Testing/src/TaskExtensions.cs b/src/Testing/src/TaskExtensions.cs index 83130aeae4..f99bf7361a 100644 --- a/src/Testing/src/TaskExtensions.cs +++ b/src/Testing/src/TaskExtensions.cs @@ -1,7 +1,8 @@ -// Copyright (c) .NET Foundation. All rights reserved. +// 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.Diagnostics; using System.Runtime.CompilerServices; using System.Threading; using System.Threading.Tasks; @@ -12,10 +13,11 @@ namespace Microsoft.AspNetCore.Testing { public static async Task TimeoutAfter(this Task task, TimeSpan timeout, [CallerFilePath] string filePath = null, - [CallerLineNumber] int lineNumber = default(int)) + [CallerLineNumber] int lineNumber = default) { // Don't create a timer if the task is already completed - if (task.IsCompleted) + // or the debugger is attached + if (task.IsCompleted || Debugger.IsAttached) { return await task; } @@ -28,17 +30,17 @@ namespace Microsoft.AspNetCore.Testing } else { - throw new TimeoutException( - CreateMessage(timeout, filePath, lineNumber)); + throw new TimeoutException(CreateMessage(timeout, filePath, lineNumber)); } } public static async Task TimeoutAfter(this Task task, TimeSpan timeout, [CallerFilePath] string filePath = null, - [CallerLineNumber] int lineNumber = default(int)) + [CallerLineNumber] int lineNumber = default) { // Don't create a timer if the task is already completed - if (task.IsCompleted) + // or the debugger is attached + if (task.IsCompleted || Debugger.IsAttached) { await task; return; From b51be2c25045ba50909822f971425da764fed2fd Mon Sep 17 00:00:00 2001 From: John Luo Date: Mon, 7 Jan 2019 13:42:22 -0500 Subject: [PATCH 0043/1101] Add attribute to allow LoggedTest to collect dump on failure (#905) * Add attribute for collecting dumps for failed LoggedTest --- src/Testing/src/CollectDumpAttribute.cs | 18 +++++ .../DumpCollector/DumpCollector.Windows.cs | 76 +++++++++++++++++++ .../src/DumpCollector/DumpCollector.cs | 20 +++++ 3 files changed, 114 insertions(+) create mode 100644 src/Testing/src/CollectDumpAttribute.cs create mode 100644 src/Testing/src/DumpCollector/DumpCollector.Windows.cs create mode 100644 src/Testing/src/DumpCollector/DumpCollector.cs diff --git a/src/Testing/src/CollectDumpAttribute.cs b/src/Testing/src/CollectDumpAttribute.cs new file mode 100644 index 0000000000..8a6aa84bac --- /dev/null +++ b/src/Testing/src/CollectDumpAttribute.cs @@ -0,0 +1,18 @@ +// 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; + +namespace Microsoft.Extensions.Logging.Testing +{ + /// + /// Capture the memory dump upon test failure. + /// + /// + /// This currently only works in Windows environments + /// + [AttributeUsage(AttributeTargets.Method, AllowMultiple = false)] + public class CollectDumpAttribute : Attribute + { + } +} diff --git a/src/Testing/src/DumpCollector/DumpCollector.Windows.cs b/src/Testing/src/DumpCollector/DumpCollector.Windows.cs new file mode 100644 index 0000000000..8d4168c20c --- /dev/null +++ b/src/Testing/src/DumpCollector/DumpCollector.Windows.cs @@ -0,0 +1,76 @@ +// 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.Diagnostics; +using System.IO; +using System.Runtime.InteropServices; +using Microsoft.Win32.SafeHandles; + +namespace Microsoft.Extensions.Logging.Testing +{ + public static partial class DumpCollector + { + private static class Windows + { + internal static void Collect(Process process, string outputFile) + { + // Open the file for writing + using (var stream = new FileStream(outputFile, FileMode.Create, FileAccess.ReadWrite, FileShare.None)) + { + // Dump the process! + var exceptionInfo = new NativeMethods.MINIDUMP_EXCEPTION_INFORMATION(); + if (!NativeMethods.MiniDumpWriteDump(process.Handle, (uint)process.Id, stream.SafeFileHandle, NativeMethods.MINIDUMP_TYPE.MiniDumpWithFullMemory, ref exceptionInfo, IntPtr.Zero, IntPtr.Zero)) + { + var err = Marshal.GetHRForLastWin32Error(); + Marshal.ThrowExceptionForHR(err); + } + } + } + + private static class NativeMethods + { + [DllImport("Dbghelp.dll")] + public static extern bool MiniDumpWriteDump(IntPtr hProcess, uint ProcessId, SafeFileHandle hFile, MINIDUMP_TYPE DumpType, ref MINIDUMP_EXCEPTION_INFORMATION ExceptionParam, IntPtr UserStreamParam, IntPtr CallbackParam); + + [StructLayout(LayoutKind.Sequential, Pack = 4)] + public struct MINIDUMP_EXCEPTION_INFORMATION + { + public uint ThreadId; + public IntPtr ExceptionPointers; + public int ClientPointers; + } + + [Flags] + public enum MINIDUMP_TYPE : uint + { + MiniDumpNormal = 0, + MiniDumpWithDataSegs = 1 << 0, + MiniDumpWithFullMemory = 1 << 1, + MiniDumpWithHandleData = 1 << 2, + MiniDumpFilterMemory = 1 << 3, + MiniDumpScanMemory = 1 << 4, + MiniDumpWithUnloadedModules = 1 << 5, + MiniDumpWithIndirectlyReferencedMemory = 1 << 6, + MiniDumpFilterModulePaths = 1 << 7, + MiniDumpWithProcessThreadData = 1 << 8, + MiniDumpWithPrivateReadWriteMemory = 1 << 9, + MiniDumpWithoutOptionalData = 1 << 10, + MiniDumpWithFullMemoryInfo = 1 << 11, + MiniDumpWithThreadInfo = 1 << 12, + MiniDumpWithCodeSegs = 1 << 13, + MiniDumpWithoutAuxiliaryState = 1 << 14, + MiniDumpWithFullAuxiliaryState = 1 << 15, + MiniDumpWithPrivateWriteCopyMemory = 1 << 16, + MiniDumpIgnoreInaccessibleMemory = 1 << 17, + MiniDumpWithTokenInformation = 1 << 18, + MiniDumpWithModuleHeaders = 1 << 19, + MiniDumpFilterTriage = 1 << 20, + MiniDumpWithAvxXStateContext = 1 << 21, + MiniDumpWithIptTrace = 1 << 22, + MiniDumpValidTypeFlags = (-1) ^ ((~1) << 22) + } + } + } + } +} diff --git a/src/Testing/src/DumpCollector/DumpCollector.cs b/src/Testing/src/DumpCollector/DumpCollector.cs new file mode 100644 index 0000000000..67043ed827 --- /dev/null +++ b/src/Testing/src/DumpCollector/DumpCollector.cs @@ -0,0 +1,20 @@ +// 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.Diagnostics; +using System.Runtime.InteropServices; + +namespace Microsoft.Extensions.Logging.Testing +{ + public static partial class DumpCollector + { + public static void Collect(Process process, string fileName) + { + if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) + { + Windows.Collect(process, fileName); + } + // No implementations yet for macOS and Linux + } + } +} From 3db4f50eace48876c30c9293363c7c83fcf2645b Mon Sep 17 00:00:00 2001 From: Nate McMaster Date: Mon, 7 Jan 2019 15:52:44 -0800 Subject: [PATCH 0044/1101] Remove netcoreapp2.0 test TFMs (dotnet/extensions#907) .NET Core 2.0 reached EOL last year. This removes multi-targeting our test projects and test assets to only use .NET Core 2.1 and .NET Framework 4.6.1.\n\nCommit migrated from https://github.com/dotnet/extensions/commit/084494d21dc5b9df06e4c3771d1232f0a1d0b3a6 --- .../Microsoft.Extensions.Configuration.KeyPerFile.Tests.csproj | 2 +- .../Microsoft.Extensions.FileProviders.Embedded.Tests.csproj | 2 +- ....Extensions.FileProviders.Embedded.Manifest.Task.Test.csproj | 2 +- .../test/Microsoft.Extensions.Localization.Tests.csproj | 2 +- .../test/Microsoft.Extensions.ObjectPool.Tests.csproj | 2 +- .../test/Shared.Tests/Microsoft.AspNetCore.Shared.Tests.csproj | 2 +- .../test/Microsoft.Extensions.WebEncoders.Tests.csproj | 2 +- 7 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src/Configuration.KeyPerFile/test/Microsoft.Extensions.Configuration.KeyPerFile.Tests.csproj b/src/Configuration.KeyPerFile/test/Microsoft.Extensions.Configuration.KeyPerFile.Tests.csproj index 634056a345..71ed14dff4 100644 --- a/src/Configuration.KeyPerFile/test/Microsoft.Extensions.Configuration.KeyPerFile.Tests.csproj +++ b/src/Configuration.KeyPerFile/test/Microsoft.Extensions.Configuration.KeyPerFile.Tests.csproj @@ -1,7 +1,7 @@  - $(StandardTestTfms) + netcoreapp2.1;net461 diff --git a/src/FileProviders/Embedded/test/Microsoft.Extensions.FileProviders.Embedded.Tests.csproj b/src/FileProviders/Embedded/test/Microsoft.Extensions.FileProviders.Embedded.Tests.csproj index 8703c9c508..b4c7a3987a 100644 --- a/src/FileProviders/Embedded/test/Microsoft.Extensions.FileProviders.Embedded.Tests.csproj +++ b/src/FileProviders/Embedded/test/Microsoft.Extensions.FileProviders.Embedded.Tests.csproj @@ -1,7 +1,7 @@  - $(StandardTestTfms) + netcoreapp2.1;net461 diff --git a/src/FileProviders/Manifest.MSBuildTask/test/Microsoft.Extensions.FileProviders.Embedded.Manifest.Task.Test.csproj b/src/FileProviders/Manifest.MSBuildTask/test/Microsoft.Extensions.FileProviders.Embedded.Manifest.Task.Test.csproj index fe33897603..9659b189c8 100644 --- a/src/FileProviders/Manifest.MSBuildTask/test/Microsoft.Extensions.FileProviders.Embedded.Manifest.Task.Test.csproj +++ b/src/FileProviders/Manifest.MSBuildTask/test/Microsoft.Extensions.FileProviders.Embedded.Manifest.Task.Test.csproj @@ -1,7 +1,7 @@  - $(StandardTestTfms) + netcoreapp2.1;net461 diff --git a/src/Localization/Localization/test/Microsoft.Extensions.Localization.Tests.csproj b/src/Localization/Localization/test/Microsoft.Extensions.Localization.Tests.csproj index e7a47a1a6e..6834de2bd5 100644 --- a/src/Localization/Localization/test/Microsoft.Extensions.Localization.Tests.csproj +++ b/src/Localization/Localization/test/Microsoft.Extensions.Localization.Tests.csproj @@ -1,7 +1,7 @@  - $(StandardTestTfms) + netcoreapp2.1;net461 diff --git a/src/ObjectPool/test/Microsoft.Extensions.ObjectPool.Tests.csproj b/src/ObjectPool/test/Microsoft.Extensions.ObjectPool.Tests.csproj index b922cc227e..c8ba4cf4df 100644 --- a/src/ObjectPool/test/Microsoft.Extensions.ObjectPool.Tests.csproj +++ b/src/ObjectPool/test/Microsoft.Extensions.ObjectPool.Tests.csproj @@ -1,7 +1,7 @@ - $(StandardTestTfms) + netcoreapp2.1;net461 diff --git a/src/Shared/test/Shared.Tests/Microsoft.AspNetCore.Shared.Tests.csproj b/src/Shared/test/Shared.Tests/Microsoft.AspNetCore.Shared.Tests.csproj index 1873e485a9..e3194d66c5 100644 --- a/src/Shared/test/Shared.Tests/Microsoft.AspNetCore.Shared.Tests.csproj +++ b/src/Shared/test/Shared.Tests/Microsoft.AspNetCore.Shared.Tests.csproj @@ -1,7 +1,7 @@ - $(StandardTestTfms) + netcoreapp2.1;net461 true diff --git a/src/WebEncoders/test/Microsoft.Extensions.WebEncoders.Tests.csproj b/src/WebEncoders/test/Microsoft.Extensions.WebEncoders.Tests.csproj index 729dc5c61a..af02c91cdf 100755 --- a/src/WebEncoders/test/Microsoft.Extensions.WebEncoders.Tests.csproj +++ b/src/WebEncoders/test/Microsoft.Extensions.WebEncoders.Tests.csproj @@ -1,7 +1,7 @@  - $(StandardTestTfms) + netcoreapp2.1;net461 From 97bb8897cf92da0a371526d72625fcaf93348ae5 Mon Sep 17 00:00:00 2001 From: Pranav K Date: Wed, 9 Jan 2019 11:10:09 -0800 Subject: [PATCH 0045/1101] Reorganize code \n\nCommit migrated from https://github.com/dotnet/extensions/commit/a24412cc03f496b928da34f8b24d092d3d64d4f5 --- src/JSInterop/.editorconfig | 27 + src/JSInterop/Microsoft.JSInterop.sln | 58 + src/JSInterop/README.md | 8 + .../src/Microsoft.JSInterop.JS/.gitignore | 1 + .../Microsoft.JSInterop.JS.csproj | 13 + .../Microsoft.JSInterop.JS/package-lock.json | 105 + .../src/Microsoft.JSInterop.JS/package.json | 25 + .../src/Microsoft.JSInterop.ts | 287 +++ .../src/Microsoft.JSInterop.JS/tsconfig.json | 19 + .../Microsoft.JSInterop/DotNetDispatcher.cs | 299 +++ .../Microsoft.JSInterop/DotNetObjectRef.cs | 66 + .../ICustomArgSerializer.cs | 22 + .../IJSInProcessRuntime.cs | 20 + .../src/Microsoft.JSInterop/IJSRuntime.cs | 32 + .../InteropArgSerializerStrategy.cs | 121 + .../Microsoft.JSInterop/JSAsyncCallResult.cs | 36 + .../src/Microsoft.JSInterop/JSException.cs | 21 + .../JSInProcessRuntimeBase.cs | 32 + .../JSInvokableAttribute.cs | 48 + .../src/Microsoft.JSInterop/JSRuntime.cs | 34 + .../src/Microsoft.JSInterop/JSRuntimeBase.cs | 116 + .../src/Microsoft.JSInterop/Json/CamelCase.cs | 59 + .../src/Microsoft.JSInterop/Json/Json.cs | 39 + .../Json/SimpleJson/README.txt | 24 + .../Json/SimpleJson/SimpleJson.cs | 2201 +++++++++++++++++ .../Microsoft.JSInterop.csproj | 7 + .../Properties/AssemblyInfo.cs | 3 + .../Microsoft.JSInterop/TaskGenericsUtil.cs | 116 + .../Mono.WebAssembly.Interop/InternalCalls.cs | 25 + .../Mono.WebAssembly.Interop.csproj | 11 + .../MonoWebAssemblyJSRuntime.cs | 114 + .../DotNetDispatcherTest.cs | 443 ++++ .../DotNetObjectRefTest.cs | 68 + .../JSInProcessRuntimeBaseTest.cs | 117 + .../JSRuntimeBaseTest.cs | 191 ++ .../Microsoft.JSInterop.Test/JSRuntimeTest.cs | 37 + .../Microsoft.JSInterop.Test/JsonUtilTest.cs | 349 +++ .../Microsoft.JSInterop.Test.csproj | 20 + 38 files changed, 5214 insertions(+) create mode 100644 src/JSInterop/.editorconfig create mode 100644 src/JSInterop/Microsoft.JSInterop.sln create mode 100644 src/JSInterop/README.md create mode 100644 src/JSInterop/src/Microsoft.JSInterop.JS/.gitignore create mode 100644 src/JSInterop/src/Microsoft.JSInterop.JS/Microsoft.JSInterop.JS.csproj create mode 100644 src/JSInterop/src/Microsoft.JSInterop.JS/package-lock.json create mode 100644 src/JSInterop/src/Microsoft.JSInterop.JS/package.json create mode 100644 src/JSInterop/src/Microsoft.JSInterop.JS/src/Microsoft.JSInterop.ts create mode 100644 src/JSInterop/src/Microsoft.JSInterop.JS/tsconfig.json create mode 100644 src/JSInterop/src/Microsoft.JSInterop/DotNetDispatcher.cs create mode 100644 src/JSInterop/src/Microsoft.JSInterop/DotNetObjectRef.cs create mode 100644 src/JSInterop/src/Microsoft.JSInterop/ICustomArgSerializer.cs create mode 100644 src/JSInterop/src/Microsoft.JSInterop/IJSInProcessRuntime.cs create mode 100644 src/JSInterop/src/Microsoft.JSInterop/IJSRuntime.cs create mode 100644 src/JSInterop/src/Microsoft.JSInterop/InteropArgSerializerStrategy.cs create mode 100644 src/JSInterop/src/Microsoft.JSInterop/JSAsyncCallResult.cs create mode 100644 src/JSInterop/src/Microsoft.JSInterop/JSException.cs create mode 100644 src/JSInterop/src/Microsoft.JSInterop/JSInProcessRuntimeBase.cs create mode 100644 src/JSInterop/src/Microsoft.JSInterop/JSInvokableAttribute.cs create mode 100644 src/JSInterop/src/Microsoft.JSInterop/JSRuntime.cs create mode 100644 src/JSInterop/src/Microsoft.JSInterop/JSRuntimeBase.cs create mode 100644 src/JSInterop/src/Microsoft.JSInterop/Json/CamelCase.cs create mode 100644 src/JSInterop/src/Microsoft.JSInterop/Json/Json.cs create mode 100644 src/JSInterop/src/Microsoft.JSInterop/Json/SimpleJson/README.txt create mode 100644 src/JSInterop/src/Microsoft.JSInterop/Json/SimpleJson/SimpleJson.cs create mode 100644 src/JSInterop/src/Microsoft.JSInterop/Microsoft.JSInterop.csproj create mode 100644 src/JSInterop/src/Microsoft.JSInterop/Properties/AssemblyInfo.cs create mode 100644 src/JSInterop/src/Microsoft.JSInterop/TaskGenericsUtil.cs create mode 100644 src/JSInterop/src/Mono.WebAssembly.Interop/InternalCalls.cs create mode 100644 src/JSInterop/src/Mono.WebAssembly.Interop/Mono.WebAssembly.Interop.csproj create mode 100644 src/JSInterop/src/Mono.WebAssembly.Interop/MonoWebAssemblyJSRuntime.cs create mode 100644 src/JSInterop/test/Microsoft.JSInterop.Test/DotNetDispatcherTest.cs create mode 100644 src/JSInterop/test/Microsoft.JSInterop.Test/DotNetObjectRefTest.cs create mode 100644 src/JSInterop/test/Microsoft.JSInterop.Test/JSInProcessRuntimeBaseTest.cs create mode 100644 src/JSInterop/test/Microsoft.JSInterop.Test/JSRuntimeBaseTest.cs create mode 100644 src/JSInterop/test/Microsoft.JSInterop.Test/JSRuntimeTest.cs create mode 100644 src/JSInterop/test/Microsoft.JSInterop.Test/JsonUtilTest.cs create mode 100644 src/JSInterop/test/Microsoft.JSInterop.Test/Microsoft.JSInterop.Test.csproj diff --git a/src/JSInterop/.editorconfig b/src/JSInterop/.editorconfig new file mode 100644 index 0000000000..0d238f8e41 --- /dev/null +++ b/src/JSInterop/.editorconfig @@ -0,0 +1,27 @@ +# All Files +[*] +charset = utf-8 +end_of_line = crlf +indent_style = space +indent_size = 4 +insert_final_newline = false +trim_trailing_whitespace = true + +# Solution Files +[*.sln] +indent_style = tab + +# Markdown Files +[*.md] +trim_trailing_whitespace = false + +# Web Files +[*.{htm,html,js,ts,css,scss,less}] +insert_final_newline = true +indent_size = 2 + +[*.{yml,json}] +indent_size = 2 + +[*.{xml,csproj,config,*proj,targets,props}] +indent_size = 2 diff --git a/src/JSInterop/Microsoft.JSInterop.sln b/src/JSInterop/Microsoft.JSInterop.sln new file mode 100644 index 0000000000..d646a6b068 --- /dev/null +++ b/src/JSInterop/Microsoft.JSInterop.sln @@ -0,0 +1,58 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio 15 +VisualStudioVersion = 15.0.27703.2042 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{1290437E-A890-419E-A317-D0F7FEE185A5}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "test", "test", "{B98D4F51-88FB-471C-B56F-752E8EE502E7}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.JSInterop", "src\Microsoft.JSInterop\Microsoft.JSInterop.csproj", "{CB4CD4A6-9BAA-46D1-944F-CE56DEC2663C}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.JSInterop.Test", "test\Microsoft.JSInterop.Test\Microsoft.JSInterop.Test.csproj", "{7FF8B199-52C0-4DFE-A73B-0C9E18220C0E}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.JSInterop.JS", "src\Microsoft.JSInterop.JS\Microsoft.JSInterop.JS.csproj", "{60BA5AAD-264A-437E-8319-577841C66CC6}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{BE4CBB33-5C40-4A07-B6FC-1D7C3AE13024}" + ProjectSection(SolutionItems) = preProject + README.md = README.md + EndProjectSection +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Mono.WebAssembly.Interop", "src\Mono.WebAssembly.Interop\Mono.WebAssembly.Interop.csproj", "{10145E99-1B2D-40C5-9595-582BDAF3E024}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {CB4CD4A6-9BAA-46D1-944F-CE56DEC2663C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {CB4CD4A6-9BAA-46D1-944F-CE56DEC2663C}.Debug|Any CPU.Build.0 = Debug|Any CPU + {CB4CD4A6-9BAA-46D1-944F-CE56DEC2663C}.Release|Any CPU.ActiveCfg = Release|Any CPU + {CB4CD4A6-9BAA-46D1-944F-CE56DEC2663C}.Release|Any CPU.Build.0 = Release|Any CPU + {7FF8B199-52C0-4DFE-A73B-0C9E18220C0E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {7FF8B199-52C0-4DFE-A73B-0C9E18220C0E}.Debug|Any CPU.Build.0 = Debug|Any CPU + {7FF8B199-52C0-4DFE-A73B-0C9E18220C0E}.Release|Any CPU.ActiveCfg = Release|Any CPU + {7FF8B199-52C0-4DFE-A73B-0C9E18220C0E}.Release|Any CPU.Build.0 = Release|Any CPU + {60BA5AAD-264A-437E-8319-577841C66CC6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {60BA5AAD-264A-437E-8319-577841C66CC6}.Debug|Any CPU.Build.0 = Debug|Any CPU + {60BA5AAD-264A-437E-8319-577841C66CC6}.Release|Any CPU.ActiveCfg = Release|Any CPU + {60BA5AAD-264A-437E-8319-577841C66CC6}.Release|Any CPU.Build.0 = Release|Any CPU + {10145E99-1B2D-40C5-9595-582BDAF3E024}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {10145E99-1B2D-40C5-9595-582BDAF3E024}.Debug|Any CPU.Build.0 = Debug|Any CPU + {10145E99-1B2D-40C5-9595-582BDAF3E024}.Release|Any CPU.ActiveCfg = Release|Any CPU + {10145E99-1B2D-40C5-9595-582BDAF3E024}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(NestedProjects) = preSolution + {CB4CD4A6-9BAA-46D1-944F-CE56DEC2663C} = {1290437E-A890-419E-A317-D0F7FEE185A5} + {7FF8B199-52C0-4DFE-A73B-0C9E18220C0E} = {B98D4F51-88FB-471C-B56F-752E8EE502E7} + {60BA5AAD-264A-437E-8319-577841C66CC6} = {1290437E-A890-419E-A317-D0F7FEE185A5} + {10145E99-1B2D-40C5-9595-582BDAF3E024} = {1290437E-A890-419E-A317-D0F7FEE185A5} + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {7E07ABF2-427A-43FA-A6A4-82B21B96ACAF} + EndGlobalSection +EndGlobal diff --git a/src/JSInterop/README.md b/src/JSInterop/README.md new file mode 100644 index 0000000000..dcdaf7618e --- /dev/null +++ b/src/JSInterop/README.md @@ -0,0 +1,8 @@ +# jsinterop + +This repo is for `Microsoft.JSInterop`, a package that provides abstractions and features for interop between .NET and JavaScript code. + +## Usage + +The primary use case is for applications built with Mono WebAssembly or Blazor. It's not expected that developers will typically use these libraries separately from Mono WebAssembly, Blazor, or a similar technology. + diff --git a/src/JSInterop/src/Microsoft.JSInterop.JS/.gitignore b/src/JSInterop/src/Microsoft.JSInterop.JS/.gitignore new file mode 100644 index 0000000000..849ddff3b7 --- /dev/null +++ b/src/JSInterop/src/Microsoft.JSInterop.JS/.gitignore @@ -0,0 +1 @@ +dist/ diff --git a/src/JSInterop/src/Microsoft.JSInterop.JS/Microsoft.JSInterop.JS.csproj b/src/JSInterop/src/Microsoft.JSInterop.JS/Microsoft.JSInterop.JS.csproj new file mode 100644 index 0000000000..4a6ba93ebb --- /dev/null +++ b/src/JSInterop/src/Microsoft.JSInterop.JS/Microsoft.JSInterop.JS.csproj @@ -0,0 +1,13 @@ + + + + netstandard2.0 + Latest + false + + + + + + + diff --git a/src/JSInterop/src/Microsoft.JSInterop.JS/package-lock.json b/src/JSInterop/src/Microsoft.JSInterop.JS/package-lock.json new file mode 100644 index 0000000000..a7f31ead4f --- /dev/null +++ b/src/JSInterop/src/Microsoft.JSInterop.JS/package-lock.json @@ -0,0 +1,105 @@ +{ + "name": "@dotnet/jsinterop", + "version": "0.1.1", + "lockfileVersion": 1, + "requires": true, + "dependencies": { + "balanced-match": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", + "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=", + "dev": true + }, + "brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "requires": { + "balanced-match": "1.0.0", + "concat-map": "0.0.1" + } + }, + "concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=", + "dev": true + }, + "fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=", + "dev": true + }, + "glob": { + "version": "7.1.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.3.tgz", + "integrity": "sha512-vcfuiIxogLV4DlGBHIUOwI0IbrJ8HWPc4MU7HzviGeNho/UJDfi6B5p3sHeWIQ0KGIU0Jpxi5ZHxemQfLkkAwQ==", + "dev": true, + "requires": { + "fs.realpath": "1.0.0", + "inflight": "1.0.6", + "inherits": "2.0.3", + "minimatch": "3.0.4", + "once": "1.4.0", + "path-is-absolute": "1.0.1" + } + }, + "inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", + "dev": true, + "requires": { + "once": "1.4.0", + "wrappy": "1.0.2" + } + }, + "inherits": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", + "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=", + "dev": true + }, + "minimatch": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", + "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", + "dev": true, + "requires": { + "brace-expansion": "1.1.11" + } + }, + "once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", + "dev": true, + "requires": { + "wrappy": "1.0.2" + } + }, + "path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=", + "dev": true + }, + "rimraf": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.6.2.tgz", + "integrity": "sha512-lreewLK/BlghmxtfH36YYVg1i8IAce4TI7oao75I1g245+6BctqTVQiBP3YUJ9C6DQOXJmkYR9X9fCLtCOJc5w==", + "dev": true, + "requires": { + "glob": "7.1.3" + } + }, + "wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=", + "dev": true + } + } +} diff --git a/src/JSInterop/src/Microsoft.JSInterop.JS/package.json b/src/JSInterop/src/Microsoft.JSInterop.JS/package.json new file mode 100644 index 0000000000..a84734bc46 --- /dev/null +++ b/src/JSInterop/src/Microsoft.JSInterop.JS/package.json @@ -0,0 +1,25 @@ +{ + "name": "@dotnet/jsinterop", + "version": "0.1.1", + "description": "Provides abstractions and features for interop between .NET and JavaScript code.", + "main": "dist/Microsoft.JSInterop.js", + "types": "dist/Microsoft.JSInterop.d.js", + "scripts": { + "prepublish": "rimraf dist && dotnet build && echo 'Finished building NPM package \"@dotnet/jsinterop\"'" + }, + "files": [ + "dist/**" + ], + "author": "Microsoft", + "license": "Apache-2.0", + "bugs": { + "url": "https://github.com/dotnet/jsinterop/issues" + }, + "repository": { + "type": "git", + "url": "https://github.com/dotnet/jsinterop.git" + }, + "devDependencies": { + "rimraf": "^2.5.4" + } +} diff --git a/src/JSInterop/src/Microsoft.JSInterop.JS/src/Microsoft.JSInterop.ts b/src/JSInterop/src/Microsoft.JSInterop.JS/src/Microsoft.JSInterop.ts new file mode 100644 index 0000000000..4b5d409d0f --- /dev/null +++ b/src/JSInterop/src/Microsoft.JSInterop.JS/src/Microsoft.JSInterop.ts @@ -0,0 +1,287 @@ +// This is a single-file self-contained module to avoid the need for a Webpack build + +module DotNet { + (window as any).DotNet = DotNet; // Ensure reachable from anywhere + + export type JsonReviver = ((key: any, value: any) => any); + const jsonRevivers: JsonReviver[] = []; + + const pendingAsyncCalls: { [id: number]: PendingAsyncCall } = {}; + const cachedJSFunctions: { [identifier: string]: Function } = {}; + let nextAsyncCallId = 1; // Start at 1 because zero signals "no response needed" + + let dotNetDispatcher: DotNetCallDispatcher | null = null; + + /** + * Sets the specified .NET call dispatcher as the current instance so that it will be used + * for future invocations. + * + * @param dispatcher An object that can dispatch calls from JavaScript to a .NET runtime. + */ + export function attachDispatcher(dispatcher: DotNetCallDispatcher) { + dotNetDispatcher = dispatcher; + } + + /** + * Adds a JSON reviver callback that will be used when parsing arguments received from .NET. + * @param reviver The reviver to add. + */ + export function attachReviver(reviver: JsonReviver) { + jsonRevivers.push(reviver); + } + + /** + * Invokes the specified .NET public method synchronously. Not all hosting scenarios support + * synchronous invocation, so if possible use invokeMethodAsync instead. + * + * @param assemblyName The short name (without key/version or .dll extension) of the .NET assembly containing the method. + * @param methodIdentifier The identifier of the method to invoke. The method must have a [JSInvokable] attribute specifying this identifier. + * @param args Arguments to pass to the method, each of which must be JSON-serializable. + * @returns The result of the operation. + */ + export function invokeMethod(assemblyName: string, methodIdentifier: string, ...args: any[]): T { + return invokePossibleInstanceMethod(assemblyName, methodIdentifier, null, args); + } + + /** + * Invokes the specified .NET public method asynchronously. + * + * @param assemblyName The short name (without key/version or .dll extension) of the .NET assembly containing the method. + * @param methodIdentifier The identifier of the method to invoke. The method must have a [JSInvokable] attribute specifying this identifier. + * @param args Arguments to pass to the method, each of which must be JSON-serializable. + * @returns A promise representing the result of the operation. + */ + export function invokeMethodAsync(assemblyName: string, methodIdentifier: string, ...args: any[]): Promise { + return invokePossibleInstanceMethodAsync(assemblyName, methodIdentifier, null, args); + } + + function invokePossibleInstanceMethod(assemblyName: string | null, methodIdentifier: string, dotNetObjectId: number | null, args: any[]): T { + const dispatcher = getRequiredDispatcher(); + if (dispatcher.invokeDotNetFromJS) { + const argsJson = JSON.stringify(args, argReplacer); + const resultJson = dispatcher.invokeDotNetFromJS(assemblyName, methodIdentifier, dotNetObjectId, argsJson); + return resultJson ? parseJsonWithRevivers(resultJson) : null; + } else { + throw new Error('The current dispatcher does not support synchronous calls from JS to .NET. Use invokeMethodAsync instead.'); + } + } + + function invokePossibleInstanceMethodAsync(assemblyName: string | null, methodIdentifier: string, dotNetObjectId: number | null, args: any[]): Promise { + const asyncCallId = nextAsyncCallId++; + const resultPromise = new Promise((resolve, reject) => { + pendingAsyncCalls[asyncCallId] = { resolve, reject }; + }); + + try { + const argsJson = JSON.stringify(args, argReplacer); + getRequiredDispatcher().beginInvokeDotNetFromJS(asyncCallId, assemblyName, methodIdentifier, dotNetObjectId, argsJson); + } catch(ex) { + // Synchronous failure + completePendingCall(asyncCallId, false, ex); + } + + return resultPromise; + } + + function getRequiredDispatcher(): DotNetCallDispatcher { + if (dotNetDispatcher !== null) { + return dotNetDispatcher; + } + + throw new Error('No .NET call dispatcher has been set.'); + } + + function completePendingCall(asyncCallId: number, success: boolean, resultOrError: any) { + if (!pendingAsyncCalls.hasOwnProperty(asyncCallId)) { + throw new Error(`There is no pending async call with ID ${asyncCallId}.`); + } + + const asyncCall = pendingAsyncCalls[asyncCallId]; + delete pendingAsyncCalls[asyncCallId]; + if (success) { + asyncCall.resolve(resultOrError); + } else { + asyncCall.reject(resultOrError); + } + } + + interface PendingAsyncCall { + resolve: (value?: T | PromiseLike) => void; + reject: (reason?: any) => void; + } + + /** + * Represents the ability to dispatch calls from JavaScript to a .NET runtime. + */ + export interface DotNetCallDispatcher { + /** + * Optional. If implemented, invoked by the runtime to perform a synchronous call to a .NET method. + * + * @param assemblyName The short name (without key/version or .dll extension) of the .NET assembly holding the method to invoke. The value may be null when invoking instance methods. + * @param methodIdentifier The identifier of the method to invoke. The method must have a [JSInvokable] attribute specifying this identifier. + * @param dotNetObjectId If given, the call will be to an instance method on the specified DotNetObject. Pass null or undefined to call static methods. + * @param argsJson JSON representation of arguments to pass to the method. + * @returns JSON representation of the result of the invocation. + */ + invokeDotNetFromJS?(assemblyName: string | null, methodIdentifier: string, dotNetObjectId: number | null, argsJson: string): string | null; + + /** + * Invoked by the runtime to begin an asynchronous call to a .NET method. + * + * @param callId A value identifying the asynchronous operation. This value should be passed back in a later call from .NET to JS. + * @param assemblyName The short name (without key/version or .dll extension) of the .NET assembly holding the method to invoke. The value may be null when invoking instance methods. + * @param methodIdentifier The identifier of the method to invoke. The method must have a [JSInvokable] attribute specifying this identifier. + * @param dotNetObjectId If given, the call will be to an instance method on the specified DotNetObject. Pass null to call static methods. + * @param argsJson JSON representation of arguments to pass to the method. + */ + beginInvokeDotNetFromJS(callId: number, assemblyName: string | null, methodIdentifier: string, dotNetObjectId: number | null, argsJson: string): void; + } + + /** + * Receives incoming calls from .NET and dispatches them to JavaScript. + */ + export const jsCallDispatcher = { + /** + * Finds the JavaScript function matching the specified identifier. + * + * @param identifier Identifies the globally-reachable function to be returned. + * @returns A Function instance. + */ + findJSFunction, + + /** + * Invokes the specified synchronous JavaScript function. + * + * @param identifier Identifies the globally-reachable function to invoke. + * @param argsJson JSON representation of arguments to be passed to the function. + * @returns JSON representation of the invocation result. + */ + invokeJSFromDotNet: (identifier: string, argsJson: string) => { + const result = findJSFunction(identifier).apply(null, parseJsonWithRevivers(argsJson)); + return result === null || result === undefined + ? null + : JSON.stringify(result, argReplacer); + }, + + /** + * Invokes the specified synchronous or asynchronous JavaScript function. + * + * @param asyncHandle A value identifying the asynchronous operation. This value will be passed back in a later call to endInvokeJSFromDotNet. + * @param identifier Identifies the globally-reachable function to invoke. + * @param argsJson JSON representation of arguments to be passed to the function. + */ + beginInvokeJSFromDotNet: (asyncHandle: number, identifier: string, argsJson: string): void => { + // Coerce synchronous functions into async ones, plus treat + // synchronous exceptions the same as async ones + const promise = new Promise(resolve => { + const synchronousResultOrPromise = findJSFunction(identifier).apply(null, parseJsonWithRevivers(argsJson)); + resolve(synchronousResultOrPromise); + }); + + // We only listen for a result if the caller wants to be notified about it + if (asyncHandle) { + // On completion, dispatch result back to .NET + // Not using "await" because it codegens a lot of boilerplate + promise.then( + result => getRequiredDispatcher().beginInvokeDotNetFromJS(0, 'Microsoft.JSInterop', 'DotNetDispatcher.EndInvoke', null, JSON.stringify([asyncHandle, true, result], argReplacer)), + error => getRequiredDispatcher().beginInvokeDotNetFromJS(0, 'Microsoft.JSInterop', 'DotNetDispatcher.EndInvoke', null, JSON.stringify([asyncHandle, false, formatError(error)])) + ); + } + }, + + /** + * Receives notification that an async call from JS to .NET has completed. + * @param asyncCallId The identifier supplied in an earlier call to beginInvokeDotNetFromJS. + * @param success A flag to indicate whether the operation completed successfully. + * @param resultOrExceptionMessage Either the operation result or an error message. + */ + endInvokeDotNetFromJS: (asyncCallId: string, success: boolean, resultOrExceptionMessage: any): void => { + const resultOrError = success ? resultOrExceptionMessage : new Error(resultOrExceptionMessage); + completePendingCall(parseInt(asyncCallId), success, resultOrError); + } + } + + function parseJsonWithRevivers(json: string): any { + return json ? JSON.parse(json, (key, initialValue) => { + // Invoke each reviver in order, passing the output from the previous reviver, + // so that each one gets a chance to transform the value + return jsonRevivers.reduce( + (latestValue, reviver) => reviver(key, latestValue), + initialValue + ); + }) : null; + } + + function formatError(error: any): string { + if (error instanceof Error) { + return `${error.message}\n${error.stack}`; + } else { + return error ? error.toString() : 'null'; + } + } + + function findJSFunction(identifier: string): Function { + if (cachedJSFunctions.hasOwnProperty(identifier)) { + return cachedJSFunctions[identifier]; + } + + let result: any = window; + let resultIdentifier = 'window'; + identifier.split('.').forEach(segment => { + if (segment in result) { + result = result[segment]; + resultIdentifier += '.' + segment; + } else { + throw new Error(`Could not find '${segment}' in '${resultIdentifier}'.`); + } + }); + + if (result instanceof Function) { + return result; + } else { + throw new Error(`The value '${resultIdentifier}' is not a function.`); + } + } + + class DotNetObject { + constructor(private _id: number) { + } + + public invokeMethod(methodIdentifier: string, ...args: any[]): T { + return invokePossibleInstanceMethod(null, methodIdentifier, this._id, args); + } + + public invokeMethodAsync(methodIdentifier: string, ...args: any[]): Promise { + return invokePossibleInstanceMethodAsync(null, methodIdentifier, this._id, args); + } + + public dispose() { + const promise = invokeMethodAsync( + 'Microsoft.JSInterop', + 'DotNetDispatcher.ReleaseDotNetObject', + this._id); + promise.catch(error => console.error(error)); + } + + public serializeAsArg() { + return `__dotNetObject:${this._id}`; + } + } + + const dotNetObjectValueFormat = /^__dotNetObject\:(\d+)$/; + attachReviver(function reviveDotNetObject(key: any, value: any) { + if (typeof value === 'string') { + const match = value.match(dotNetObjectValueFormat); + if (match) { + return new DotNetObject(parseInt(match[1])); + } + } + + // Unrecognized - let another reviver handle it + return value; + }); + + function argReplacer(key: string, value: any) { + return value instanceof DotNetObject ? value.serializeAsArg() : value; + } +} diff --git a/src/JSInterop/src/Microsoft.JSInterop.JS/tsconfig.json b/src/JSInterop/src/Microsoft.JSInterop.JS/tsconfig.json new file mode 100644 index 0000000000..f5a2b0e31a --- /dev/null +++ b/src/JSInterop/src/Microsoft.JSInterop.JS/tsconfig.json @@ -0,0 +1,19 @@ +{ + "compilerOptions": { + "baseUrl": ".", + "noEmitOnError": true, + "removeComments": false, + "sourceMap": true, + "target": "es5", + "lib": ["es2015", "dom", "es2015.promise"], + "strict": true, + "declaration": true, + "outDir": "dist" + }, + "include": [ + "src/**/*.ts" + ], + "exclude": [ + "dist/**" + ] +} diff --git a/src/JSInterop/src/Microsoft.JSInterop/DotNetDispatcher.cs b/src/JSInterop/src/Microsoft.JSInterop/DotNetDispatcher.cs new file mode 100644 index 0000000000..0f346bbfdd --- /dev/null +++ b/src/JSInterop/src/Microsoft.JSInterop/DotNetDispatcher.cs @@ -0,0 +1,299 @@ +// 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 Microsoft.JSInterop.Internal; +using System; +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; +using System.Threading.Tasks; + +namespace Microsoft.JSInterop +{ + /// + /// Provides methods that receive incoming calls from JS to .NET. + /// + public static class DotNetDispatcher + { + private static ConcurrentDictionary> _cachedMethodsByAssembly + = new ConcurrentDictionary>(); + + /// + /// Receives a call from JS to .NET, locating and invoking the specified method. + /// + /// The assembly containing the method to be invoked. + /// The identifier of the method to be invoked. The method must be annotated with a matching this identifier string. + /// For instance method calls, identifies the target object. + /// A JSON representation of the parameters. + /// A JSON representation of the return value, or null. + public static string Invoke(string assemblyName, string methodIdentifier, long dotNetObjectId, string argsJson) + { + // This method doesn't need [JSInvokable] because the platform is responsible for having + // some way to dispatch calls here. The logic inside here is the thing that checks whether + // the targeted method has [JSInvokable]. It is not itself subject to that restriction, + // because there would be nobody to police that. This method *is* the police. + + // DotNetDispatcher only works with JSRuntimeBase instances. + var jsRuntime = (JSRuntimeBase)JSRuntime.Current; + + var targetInstance = (object)null; + if (dotNetObjectId != default) + { + targetInstance = jsRuntime.ArgSerializerStrategy.FindDotNetObject(dotNetObjectId); + } + + var syncResult = InvokeSynchronously(assemblyName, methodIdentifier, targetInstance, argsJson); + return syncResult == null ? null : Json.Serialize(syncResult, jsRuntime.ArgSerializerStrategy); + } + + /// + /// Receives a call from JS to .NET, locating and invoking the specified method asynchronously. + /// + /// A value identifying the asynchronous call that should be passed back with the result, or null if no result notification is required. + /// The assembly containing the method to be invoked. + /// The identifier of the method to be invoked. The method must be annotated with a matching this identifier string. + /// For instance method calls, identifies the target object. + /// A JSON representation of the parameters. + /// A JSON representation of the return value, or null. + public static void BeginInvoke(string callId, string assemblyName, string methodIdentifier, long dotNetObjectId, string argsJson) + { + // This method doesn't need [JSInvokable] because the platform is responsible for having + // some way to dispatch calls here. The logic inside here is the thing that checks whether + // the targeted method has [JSInvokable]. It is not itself subject to that restriction, + // because there would be nobody to police that. This method *is* the police. + + // DotNetDispatcher only works with JSRuntimeBase instances. + // If the developer wants to use a totally custom IJSRuntime, then their JS-side + // code has to implement its own way of returning async results. + var jsRuntimeBaseInstance = (JSRuntimeBase)JSRuntime.Current; + + var targetInstance = dotNetObjectId == default + ? null + : jsRuntimeBaseInstance.ArgSerializerStrategy.FindDotNetObject(dotNetObjectId); + + object syncResult = null; + Exception syncException = null; + + try + { + syncResult = InvokeSynchronously(assemblyName, methodIdentifier, targetInstance, argsJson); + } + catch (Exception ex) + { + syncException = ex; + } + + // If there was no callId, the caller does not want to be notified about the result + if (callId != null) + { + // Invoke and coerce the result to a Task so the caller can use the same async API + // for both synchronous and asynchronous methods + var task = CoerceToTask(syncResult, syncException); + + task.ContinueWith(completedTask => + { + try + { + var result = TaskGenericsUtil.GetTaskResult(completedTask); + jsRuntimeBaseInstance.EndInvokeDotNet(callId, true, result); + } + catch (Exception ex) + { + ex = UnwrapException(ex); + jsRuntimeBaseInstance.EndInvokeDotNet(callId, false, ex); + } + }); + } + } + + private static Task CoerceToTask(object syncResult, Exception syncException) + { + if (syncException != null) + { + return Task.FromException(syncException); + } + else if (syncResult is Task syncResultTask) + { + return syncResultTask; + } + else + { + return Task.FromResult(syncResult); + } + } + + private static object InvokeSynchronously(string assemblyName, string methodIdentifier, object targetInstance, string argsJson) + { + if (targetInstance != null) + { + if (assemblyName != null) + { + throw new ArgumentException($"For instance method calls, '{nameof(assemblyName)}' should be null. Value received: '{assemblyName}'."); + } + + assemblyName = targetInstance.GetType().Assembly.GetName().Name; + } + + var (methodInfo, parameterTypes) = GetCachedMethodInfo(assemblyName, methodIdentifier); + + // There's no direct way to say we want to deserialize as an array with heterogenous + // entry types (e.g., [string, int, bool]), so we need to deserialize in two phases. + // First we deserialize as object[], for which SimpleJson will supply JsonObject + // instances for nonprimitive values. + var suppliedArgs = (object[])null; + var suppliedArgsLength = 0; + if (argsJson != null) + { + suppliedArgs = Json.Deserialize(argsJson).ToArray(); + suppliedArgsLength = suppliedArgs.Length; + } + if (suppliedArgsLength != parameterTypes.Length) + { + throw new ArgumentException($"In call to '{methodIdentifier}', expected {parameterTypes.Length} parameters but received {suppliedArgsLength}."); + } + + // Second, convert each supplied value to the type expected by the method + var runtime = (JSRuntimeBase)JSRuntime.Current; + var serializerStrategy = runtime.ArgSerializerStrategy; + for (var i = 0; i < suppliedArgsLength; i++) + { + if (parameterTypes[i] == typeof(JSAsyncCallResult)) + { + // For JS async call results, we have to defer the deserialization until + // later when we know what type it's meant to be deserialized as + suppliedArgs[i] = new JSAsyncCallResult(suppliedArgs[i]); + } + else + { + suppliedArgs[i] = serializerStrategy.DeserializeObject( + suppliedArgs[i], parameterTypes[i]); + } + } + + try + { + return methodInfo.Invoke(targetInstance, suppliedArgs); + } + catch (Exception ex) + { + throw UnwrapException(ex); + } + } + + /// + /// Receives notification that a call from .NET to JS has finished, marking the + /// associated as completed. + /// + /// The identifier for the function invocation. + /// A flag to indicate whether the invocation succeeded. + /// If is true, specifies the invocation result. If is false, gives the corresponding to the invocation failure. + [JSInvokable(nameof(DotNetDispatcher) + "." + nameof(EndInvoke))] + public static void EndInvoke(long asyncHandle, bool succeeded, JSAsyncCallResult result) + => ((JSRuntimeBase)JSRuntime.Current).EndInvokeJS(asyncHandle, succeeded, result.ResultOrException); + + /// + /// Releases the reference to the specified .NET object. This allows the .NET runtime + /// to garbage collect that object if there are no other references to it. + /// + /// To avoid leaking memory, the JavaScript side code must call this for every .NET + /// object it obtains a reference to. The exception is if that object is used for + /// the entire lifetime of a given user's session, in which case it is released + /// automatically when the JavaScript runtime is disposed. + /// + /// The identifier previously passed to JavaScript code. + [JSInvokable(nameof(DotNetDispatcher) + "." + nameof(ReleaseDotNetObject))] + public static void ReleaseDotNetObject(long dotNetObjectId) + { + // DotNetDispatcher only works with JSRuntimeBase instances. + var jsRuntime = (JSRuntimeBase)JSRuntime.Current; + jsRuntime.ArgSerializerStrategy.ReleaseDotNetObject(dotNetObjectId); + } + + private static (MethodInfo, Type[]) GetCachedMethodInfo(string assemblyName, string methodIdentifier) + { + if (string.IsNullOrWhiteSpace(assemblyName)) + { + throw new ArgumentException("Cannot be null, empty, or whitespace.", nameof(assemblyName)); + } + + if (string.IsNullOrWhiteSpace(methodIdentifier)) + { + throw new ArgumentException("Cannot be null, empty, or whitespace.", nameof(methodIdentifier)); + } + + var assemblyMethods = _cachedMethodsByAssembly.GetOrAdd(assemblyName, ScanAssemblyForCallableMethods); + if (assemblyMethods.TryGetValue(methodIdentifier, out var result)) + { + return result; + } + else + { + throw new ArgumentException($"The assembly '{assemblyName}' does not contain a public method with [{nameof(JSInvokableAttribute)}(\"{methodIdentifier}\")]."); + } + } + + private static IReadOnlyDictionary ScanAssemblyForCallableMethods(string assemblyName) + { + // TODO: Consider looking first for assembly-level attributes (i.e., if there are any, + // only use those) to avoid scanning, especially for framework assemblies. + var result = new Dictionary(); + var invokableMethods = GetRequiredLoadedAssembly(assemblyName) + .GetExportedTypes() + .SelectMany(type => type.GetMethods( + BindingFlags.Public | + BindingFlags.DeclaredOnly | + BindingFlags.Instance | + BindingFlags.Static)) + .Where(method => method.IsDefined(typeof(JSInvokableAttribute), inherit: false)); + foreach (var method in invokableMethods) + { + var identifier = method.GetCustomAttribute(false).Identifier ?? method.Name; + var parameterTypes = method.GetParameters().Select(p => p.ParameterType).ToArray(); + + try + { + result.Add(identifier, (method, parameterTypes)); + } + catch (ArgumentException) + { + if (result.ContainsKey(identifier)) + { + throw new InvalidOperationException($"The assembly '{assemblyName}' contains more than one " + + $"[JSInvokable] method with identifier '{identifier}'. All [JSInvokable] methods within the same " + + $"assembly must have different identifiers. You can pass a custom identifier as a parameter to " + + $"the [JSInvokable] attribute."); + } + else + { + throw; + } + } + } + + return result; + } + + private static Assembly GetRequiredLoadedAssembly(string assemblyName) + { + // We don't want to load assemblies on demand here, because we don't necessarily trust + // "assemblyName" to be something the developer intended to load. So only pick from the + // set of already-loaded assemblies. + // In some edge cases this might force developers to explicitly call something on the + // target assembly (from .NET) before they can invoke its allowed methods from JS. + var loadedAssemblies = AppDomain.CurrentDomain.GetAssemblies(); + return loadedAssemblies.FirstOrDefault(a => a.GetName().Name.Equals(assemblyName, StringComparison.Ordinal)) + ?? throw new ArgumentException($"There is no loaded assembly with the name '{assemblyName}'."); + } + + private static Exception UnwrapException(Exception ex) + { + while ((ex is AggregateException || ex is TargetInvocationException) && ex.InnerException != null) + { + ex = ex.InnerException; + } + + return ex; + } + } +} diff --git a/src/JSInterop/src/Microsoft.JSInterop/DotNetObjectRef.cs b/src/JSInterop/src/Microsoft.JSInterop/DotNetObjectRef.cs new file mode 100644 index 0000000000..aa62bee341 --- /dev/null +++ b/src/JSInterop/src/Microsoft.JSInterop/DotNetObjectRef.cs @@ -0,0 +1,66 @@ +// 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.Threading; + +namespace Microsoft.JSInterop +{ + /// + /// Wraps a JS interop argument, indicating that the value should not be serialized as JSON + /// but instead should be passed as a reference. + /// + /// To avoid leaking memory, the reference must later be disposed by JS code or by .NET code. + /// + public class DotNetObjectRef : IDisposable + { + /// + /// Gets the object instance represented by this wrapper. + /// + public object Value { get; } + + // We track an associated IJSRuntime purely so that this class can be IDisposable + // in the normal way. Developers are more likely to use objectRef.Dispose() than + // some less familiar API such as JSRuntime.Current.UntrackObjectRef(objectRef). + private IJSRuntime _attachedToRuntime; + + /// + /// Constructs an instance of . + /// + /// The value being wrapped. + public DotNetObjectRef(object value) + { + Value = value; + } + + /// + /// Ensures the is associated with the specified . + /// Developers do not normally need to invoke this manually, since it is called automatically by + /// framework code. + /// + /// The . + public void EnsureAttachedToJsRuntime(IJSRuntime runtime) + { + // The reason we populate _attachedToRuntime here rather than in the constructor + // is to ensure developers can't accidentally try to reuse DotNetObjectRef across + // different IJSRuntime instances. This method gets called as part of serializing + // the DotNetObjectRef during an interop call. + + var existingRuntime = Interlocked.CompareExchange(ref _attachedToRuntime, runtime, null); + if (existingRuntime != null && existingRuntime != runtime) + { + throw new InvalidOperationException($"The {nameof(DotNetObjectRef)} is already associated with a different {nameof(IJSRuntime)}. Do not attempt to re-use {nameof(DotNetObjectRef)} instances with multiple {nameof(IJSRuntime)} instances."); + } + } + + /// + /// Stops tracking this object reference, allowing it to be garbage collected + /// (if there are no other references to it). Once the instance is disposed, it + /// can no longer be used in interop calls from JavaScript code. + /// + public void Dispose() + { + _attachedToRuntime?.UntrackObjectRef(this); + } + } +} diff --git a/src/JSInterop/src/Microsoft.JSInterop/ICustomArgSerializer.cs b/src/JSInterop/src/Microsoft.JSInterop/ICustomArgSerializer.cs new file mode 100644 index 0000000000..f4012af8e9 --- /dev/null +++ b/src/JSInterop/src/Microsoft.JSInterop/ICustomArgSerializer.cs @@ -0,0 +1,22 @@ +// 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. + +namespace Microsoft.JSInterop.Internal +{ + // This is "soft" internal because we're trying to avoid expanding JsonUtil into a sophisticated + // API. Developers who want that would be better served by using a different JSON package + // instead. Also the perf implications of the ICustomArgSerializer approach aren't ideal + // (it forces structs to be boxed, and returning a dictionary means lots more allocations + // and boxing of any value-typed properties). + + /// + /// Internal. Intended for framework use only. + /// + public interface ICustomArgSerializer + { + /// + /// Internal. Intended for framework use only. + /// + object ToJsonPrimitive(); + } +} diff --git a/src/JSInterop/src/Microsoft.JSInterop/IJSInProcessRuntime.cs b/src/JSInterop/src/Microsoft.JSInterop/IJSInProcessRuntime.cs new file mode 100644 index 0000000000..cae5126db2 --- /dev/null +++ b/src/JSInterop/src/Microsoft.JSInterop/IJSInProcessRuntime.cs @@ -0,0 +1,20 @@ +// 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. + +namespace Microsoft.JSInterop +{ + /// + /// Represents an instance of a JavaScript runtime to which calls may be dispatched. + /// + public interface IJSInProcessRuntime : IJSRuntime + { + /// + /// Invokes the specified JavaScript function synchronously. + /// + /// The JSON-serializable return type. + /// An identifier for the function to invoke. For example, the value "someScope.someFunction" will invoke the function window.someScope.someFunction. + /// JSON-serializable arguments. + /// An instance of obtained by JSON-deserializing the return value. + T Invoke(string identifier, params object[] args); + } +} diff --git a/src/JSInterop/src/Microsoft.JSInterop/IJSRuntime.cs b/src/JSInterop/src/Microsoft.JSInterop/IJSRuntime.cs new file mode 100644 index 0000000000..b56d1f0089 --- /dev/null +++ b/src/JSInterop/src/Microsoft.JSInterop/IJSRuntime.cs @@ -0,0 +1,32 @@ +// 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.Threading.Tasks; + +namespace Microsoft.JSInterop +{ + /// + /// Represents an instance of a JavaScript runtime to which calls may be dispatched. + /// + public interface IJSRuntime + { + /// + /// Invokes the specified JavaScript function asynchronously. + /// + /// The JSON-serializable return type. + /// An identifier for the function to invoke. For example, the value "someScope.someFunction" will invoke the function window.someScope.someFunction. + /// JSON-serializable arguments. + /// An instance of obtained by JSON-deserializing the return value. + Task InvokeAsync(string identifier, params object[] args); + + /// + /// Stops tracking the .NET object represented by the . + /// This allows it to be garbage collected (if nothing else holds a reference to it) + /// and means the JS-side code can no longer invoke methods on the instance or pass + /// it as an argument to subsequent calls. + /// + /// The reference to stop tracking. + /// This method is called automatically by . + void UntrackObjectRef(DotNetObjectRef dotNetObjectRef); + } +} diff --git a/src/JSInterop/src/Microsoft.JSInterop/InteropArgSerializerStrategy.cs b/src/JSInterop/src/Microsoft.JSInterop/InteropArgSerializerStrategy.cs new file mode 100644 index 0000000000..663c1cf85a --- /dev/null +++ b/src/JSInterop/src/Microsoft.JSInterop/InteropArgSerializerStrategy.cs @@ -0,0 +1,121 @@ +// 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 Microsoft.JSInterop.Internal; +using SimpleJson; +using System; +using System.Collections.Generic; + +namespace Microsoft.JSInterop +{ + internal class InteropArgSerializerStrategy : PocoJsonSerializerStrategy + { + private readonly JSRuntimeBase _jsRuntime; + private const string _dotNetObjectPrefix = "__dotNetObject:"; + private object _storageLock = new object(); + private long _nextId = 1; // Start at 1, because 0 signals "no object" + private Dictionary _trackedRefsById = new Dictionary(); + private Dictionary _trackedIdsByRef = new Dictionary(); + + public InteropArgSerializerStrategy(JSRuntimeBase jsRuntime) + { + _jsRuntime = jsRuntime ?? throw new ArgumentNullException(nameof(jsRuntime)); + } + + protected override bool TrySerializeKnownTypes(object input, out object output) + { + switch (input) + { + case DotNetObjectRef marshalByRefValue: + EnsureDotNetObjectTracked(marshalByRefValue, out var id); + + // Special value format recognized by the code in Microsoft.JSInterop.js + // If we have to make it more clash-resistant, we can do + output = _dotNetObjectPrefix + id; + + return true; + + case ICustomArgSerializer customArgSerializer: + output = customArgSerializer.ToJsonPrimitive(); + return true; + + default: + return base.TrySerializeKnownTypes(input, out output); + } + } + + public override object DeserializeObject(object value, Type type) + { + if (value is string valueString) + { + if (valueString.StartsWith(_dotNetObjectPrefix)) + { + var dotNetObjectId = long.Parse(valueString.Substring(_dotNetObjectPrefix.Length)); + return FindDotNetObject(dotNetObjectId); + } + } + + return base.DeserializeObject(value, type); + } + + public object FindDotNetObject(long dotNetObjectId) + { + lock (_storageLock) + { + return _trackedRefsById.TryGetValue(dotNetObjectId, out var dotNetObjectRef) + ? dotNetObjectRef.Value + : throw new ArgumentException($"There is no tracked object with id '{dotNetObjectId}'. Perhaps the reference was already released.", nameof(dotNetObjectId)); + } + } + + /// + /// Stops tracking the specified .NET object reference. + /// This overload is typically invoked from JS code via JS interop. + /// + /// The ID of the . + public void ReleaseDotNetObject(long dotNetObjectId) + { + lock (_storageLock) + { + if (_trackedRefsById.TryGetValue(dotNetObjectId, out var dotNetObjectRef)) + { + _trackedRefsById.Remove(dotNetObjectId); + _trackedIdsByRef.Remove(dotNetObjectRef); + } + } + } + + /// + /// Stops tracking the specified .NET object reference. + /// This overload is typically invoked from .NET code by . + /// + /// The . + public void ReleaseDotNetObject(DotNetObjectRef dotNetObjectRef) + { + lock (_storageLock) + { + if (_trackedIdsByRef.TryGetValue(dotNetObjectRef, out var dotNetObjectId)) + { + _trackedRefsById.Remove(dotNetObjectId); + _trackedIdsByRef.Remove(dotNetObjectRef); + } + } + } + + private void EnsureDotNetObjectTracked(DotNetObjectRef dotNetObjectRef, out long dotNetObjectId) + { + dotNetObjectRef.EnsureAttachedToJsRuntime(_jsRuntime); + + lock (_storageLock) + { + // Assign an ID only if it doesn't already have one + if (!_trackedIdsByRef.TryGetValue(dotNetObjectRef, out dotNetObjectId)) + { + dotNetObjectId = _nextId++; + _trackedRefsById.Add(dotNetObjectId, dotNetObjectRef); + _trackedIdsByRef.Add(dotNetObjectRef, dotNetObjectId); + } + } + } + } +} diff --git a/src/JSInterop/src/Microsoft.JSInterop/JSAsyncCallResult.cs b/src/JSInterop/src/Microsoft.JSInterop/JSAsyncCallResult.cs new file mode 100644 index 0000000000..d46517eddc --- /dev/null +++ b/src/JSInterop/src/Microsoft.JSInterop/JSAsyncCallResult.cs @@ -0,0 +1,36 @@ +// 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. + +namespace Microsoft.JSInterop.Internal +{ + // This type takes care of a special case in handling the result of an async call from + // .NET to JS. The information about what type the result should be exists only on the + // corresponding TaskCompletionSource. We don't have that information at the time + // that we deserialize the incoming argsJson before calling DotNetDispatcher.EndInvoke. + // Declaring the EndInvoke parameter type as JSAsyncCallResult defers the deserialization + // until later when we have access to the TaskCompletionSource. + // + // There's no reason why developers would need anything similar to this in user code, + // because this is the mechanism by which we resolve the incoming argsJson to the correct + // user types before completing calls. + // + // It's marked as 'public' only because it has to be for use as an argument on a + // [JSInvokable] method. + + /// + /// Intended for framework use only. + /// + public class JSAsyncCallResult + { + internal object ResultOrException { get; } + + /// + /// Constructs an instance of . + /// + /// The result of the call. + internal JSAsyncCallResult(object resultOrException) + { + ResultOrException = resultOrException; + } + } +} diff --git a/src/JSInterop/src/Microsoft.JSInterop/JSException.cs b/src/JSInterop/src/Microsoft.JSInterop/JSException.cs new file mode 100644 index 0000000000..2929f69311 --- /dev/null +++ b/src/JSInterop/src/Microsoft.JSInterop/JSException.cs @@ -0,0 +1,21 @@ +// 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; + +namespace Microsoft.JSInterop +{ + /// + /// Represents errors that occur during an interop call from .NET to JavaScript. + /// + public class JSException : Exception + { + /// + /// Constructs an instance of . + /// + /// The exception message. + public JSException(string message) : base(message) + { + } + } +} diff --git a/src/JSInterop/src/Microsoft.JSInterop/JSInProcessRuntimeBase.cs b/src/JSInterop/src/Microsoft.JSInterop/JSInProcessRuntimeBase.cs new file mode 100644 index 0000000000..49a47d0595 --- /dev/null +++ b/src/JSInterop/src/Microsoft.JSInterop/JSInProcessRuntimeBase.cs @@ -0,0 +1,32 @@ +// 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. + +namespace Microsoft.JSInterop +{ + /// + /// Abstract base class for an in-process JavaScript runtime. + /// + public abstract class JSInProcessRuntimeBase : JSRuntimeBase, IJSInProcessRuntime + { + /// + /// Invokes the specified JavaScript function synchronously. + /// + /// The JSON-serializable return type. + /// An identifier for the function to invoke. For example, the value "someScope.someFunction" will invoke the function window.someScope.someFunction. + /// JSON-serializable arguments. + /// An instance of obtained by JSON-deserializing the return value. + public T Invoke(string identifier, params object[] args) + { + var resultJson = InvokeJS(identifier, Json.Serialize(args, ArgSerializerStrategy)); + return Json.Deserialize(resultJson, ArgSerializerStrategy); + } + + /// + /// Performs a synchronous function invocation. + /// + /// The identifier for the function to invoke. + /// A JSON representation of the arguments. + /// A JSON representation of the result. + protected abstract string InvokeJS(string identifier, string argsJson); + } +} diff --git a/src/JSInterop/src/Microsoft.JSInterop/JSInvokableAttribute.cs b/src/JSInterop/src/Microsoft.JSInterop/JSInvokableAttribute.cs new file mode 100644 index 0000000000..e037078cba --- /dev/null +++ b/src/JSInterop/src/Microsoft.JSInterop/JSInvokableAttribute.cs @@ -0,0 +1,48 @@ +// 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; + +namespace Microsoft.JSInterop +{ + /// + /// Identifies a .NET method as allowing invocation from JavaScript code. + /// Any method marked with this attribute may receive arbitrary parameter values + /// from untrusted callers. All inputs should be validated carefully. + /// + [AttributeUsage(AttributeTargets.Method, AllowMultiple = true)] + public class JSInvokableAttribute : Attribute + { + /// + /// Gets the identifier for the method. The identifier must be unique within the scope + /// of an assembly. + /// + /// If not set, the identifier is taken from the name of the method. In this case the + /// method name must be unique within the assembly. + /// + public string Identifier { get; } + + /// + /// Constructs an instance of without setting + /// an identifier for the method. + /// + public JSInvokableAttribute() + { + } + + /// + /// Constructs an instance of using the specified + /// identifier. + /// + /// An identifier for the method, which must be unique within the scope of the assembly. + public JSInvokableAttribute(string identifier) + { + if (string.IsNullOrEmpty(identifier)) + { + throw new ArgumentException("Cannot be null or empty", nameof(identifier)); + } + + Identifier = identifier; + } + } +} diff --git a/src/JSInterop/src/Microsoft.JSInterop/JSRuntime.cs b/src/JSInterop/src/Microsoft.JSInterop/JSRuntime.cs new file mode 100644 index 0000000000..5a9830fa35 --- /dev/null +++ b/src/JSInterop/src/Microsoft.JSInterop/JSRuntime.cs @@ -0,0 +1,34 @@ +// 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.Threading; + +namespace Microsoft.JSInterop +{ + /// + /// Provides mechanisms for accessing the current . + /// + public static class JSRuntime + { + private static AsyncLocal _currentJSRuntime + = new AsyncLocal(); + + /// + /// Gets the current , if any. + /// + public static IJSRuntime Current => _currentJSRuntime.Value; + + /// + /// Sets the current JS runtime to the supplied instance. + /// + /// This is intended for framework use. Developers should not normally need to call this method. + /// + /// The new current . + public static void SetCurrentJSRuntime(IJSRuntime instance) + { + _currentJSRuntime.Value = instance + ?? throw new ArgumentNullException(nameof(instance)); + } + } +} diff --git a/src/JSInterop/src/Microsoft.JSInterop/JSRuntimeBase.cs b/src/JSInterop/src/Microsoft.JSInterop/JSRuntimeBase.cs new file mode 100644 index 0000000000..09379396cf --- /dev/null +++ b/src/JSInterop/src/Microsoft.JSInterop/JSRuntimeBase.cs @@ -0,0 +1,116 @@ +// 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.Collections.Concurrent; +using System.Threading; +using System.Threading.Tasks; + +namespace Microsoft.JSInterop +{ + /// + /// Abstract base class for a JavaScript runtime. + /// + public abstract class JSRuntimeBase : IJSRuntime + { + private long _nextPendingTaskId = 1; // Start at 1 because zero signals "no response needed" + private readonly ConcurrentDictionary _pendingTasks + = new ConcurrentDictionary(); + + internal InteropArgSerializerStrategy ArgSerializerStrategy { get; } + + /// + /// Constructs an instance of . + /// + public JSRuntimeBase() + { + ArgSerializerStrategy = new InteropArgSerializerStrategy(this); + } + + /// + public void UntrackObjectRef(DotNetObjectRef dotNetObjectRef) + => ArgSerializerStrategy.ReleaseDotNetObject(dotNetObjectRef); + + /// + /// Invokes the specified JavaScript function asynchronously. + /// + /// The JSON-serializable return type. + /// An identifier for the function to invoke. For example, the value "someScope.someFunction" will invoke the function window.someScope.someFunction. + /// JSON-serializable arguments. + /// An instance of obtained by JSON-deserializing the return value. + public Task InvokeAsync(string identifier, params object[] args) + { + // We might consider also adding a default timeout here in case we don't want to + // risk a memory leak in the scenario where the JS-side code is failing to complete + // the operation. + + var taskId = Interlocked.Increment(ref _nextPendingTaskId); + var tcs = new TaskCompletionSource(); + _pendingTasks[taskId] = tcs; + + try + { + var argsJson = args?.Length > 0 + ? Json.Serialize(args, ArgSerializerStrategy) + : null; + BeginInvokeJS(taskId, identifier, argsJson); + return tcs.Task; + } + catch + { + _pendingTasks.TryRemove(taskId, out _); + throw; + } + } + + /// + /// Begins an asynchronous function invocation. + /// + /// The identifier for the function invocation, or zero if no async callback is required. + /// The identifier for the function to invoke. + /// A JSON representation of the arguments. + protected abstract void BeginInvokeJS(long asyncHandle, string identifier, string argsJson); + + internal void EndInvokeDotNet(string callId, bool success, object resultOrException) + { + // For failures, the common case is to call EndInvokeDotNet with the Exception object. + // For these we'll serialize as something that's useful to receive on the JS side. + // If the value is not an Exception, we'll just rely on it being directly JSON-serializable. + if (!success && resultOrException is Exception) + { + resultOrException = resultOrException.ToString(); + } + + // We pass 0 as the async handle because we don't want the JS-side code to + // send back any notification (we're just providing a result for an existing async call) + BeginInvokeJS(0, "DotNet.jsCallDispatcher.endInvokeDotNetFromJS", Json.Serialize(new[] { + callId, + success, + resultOrException + }, ArgSerializerStrategy)); + } + + internal void EndInvokeJS(long asyncHandle, bool succeeded, object resultOrException) + { + if (!_pendingTasks.TryRemove(asyncHandle, out var tcs)) + { + throw new ArgumentException($"There is no pending task with handle '{asyncHandle}'."); + } + + if (succeeded) + { + var resultType = TaskGenericsUtil.GetTaskCompletionSourceResultType(tcs); + if (resultOrException is SimpleJson.JsonObject || resultOrException is SimpleJson.JsonArray) + { + resultOrException = ArgSerializerStrategy.DeserializeObject(resultOrException, resultType); + } + + TaskGenericsUtil.SetTaskCompletionSourceResult(tcs, resultOrException); + } + else + { + TaskGenericsUtil.SetTaskCompletionSourceException(tcs, new JSException(resultOrException.ToString())); + } + } + } +} diff --git a/src/JSInterop/src/Microsoft.JSInterop/Json/CamelCase.cs b/src/JSInterop/src/Microsoft.JSInterop/Json/CamelCase.cs new file mode 100644 index 0000000000..8caae1387b --- /dev/null +++ b/src/JSInterop/src/Microsoft.JSInterop/Json/CamelCase.cs @@ -0,0 +1,59 @@ +// 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; + +namespace Microsoft.JSInterop +{ + internal static class CamelCase + { + public static string MemberNameToCamelCase(string value) + { + if (string.IsNullOrEmpty(value)) + { + throw new ArgumentException( + $"The value '{value ?? "null"}' is not a valid member name.", + nameof(value)); + } + + // If we don't need to modify the value, bail out without creating a char array + if (!char.IsUpper(value[0])) + { + return value; + } + + // We have to modify at least one character + var chars = value.ToCharArray(); + + var length = chars.Length; + if (length < 2 || !char.IsUpper(chars[1])) + { + // Only the first character needs to be modified + // Note that this branch is functionally necessary, because the 'else' branch below + // never looks at char[1]. It's always looking at the n+2 character. + chars[0] = char.ToLowerInvariant(chars[0]); + } + else + { + // If chars[0] and chars[1] are both upper, then we'll lowercase the first char plus + // any consecutive uppercase ones, stopping if we find any char that is followed by a + // non-uppercase one + var i = 0; + while (i < length) + { + chars[i] = char.ToLowerInvariant(chars[i]); + + i++; + + // If the next-plus-one char isn't also uppercase, then we're now on the last uppercase, so stop + if (i < length - 1 && !char.IsUpper(chars[i + 1])) + { + break; + } + } + } + + return new string(chars); + } + } +} diff --git a/src/JSInterop/src/Microsoft.JSInterop/Json/Json.cs b/src/JSInterop/src/Microsoft.JSInterop/Json/Json.cs new file mode 100644 index 0000000000..7275dfe427 --- /dev/null +++ b/src/JSInterop/src/Microsoft.JSInterop/Json/Json.cs @@ -0,0 +1,39 @@ +// 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. + +namespace Microsoft.JSInterop +{ + /// + /// Provides mechanisms for converting between .NET objects and JSON strings for use + /// when making calls to JavaScript functions via . + /// + /// Warning: This is not intended as a general-purpose JSON library. It is only intended + /// for use when making calls via . Eventually its implementation + /// will be replaced by something more general-purpose. + /// + public static class Json + { + /// + /// Serializes the value as a JSON string. + /// + /// The value to serialize. + /// The JSON string. + public static string Serialize(object value) + => SimpleJson.SimpleJson.SerializeObject(value); + + internal static string Serialize(object value, SimpleJson.IJsonSerializerStrategy serializerStrategy) + => SimpleJson.SimpleJson.SerializeObject(value, serializerStrategy); + + /// + /// Deserializes the JSON string, creating an object of the specified generic type. + /// + /// The type of object to create. + /// The JSON string. + /// An object of the specified type. + public static T Deserialize(string json) + => SimpleJson.SimpleJson.DeserializeObject(json); + + internal static T Deserialize(string json, SimpleJson.IJsonSerializerStrategy serializerStrategy) + => SimpleJson.SimpleJson.DeserializeObject(json, serializerStrategy); + } +} diff --git a/src/JSInterop/src/Microsoft.JSInterop/Json/SimpleJson/README.txt b/src/JSInterop/src/Microsoft.JSInterop/Json/SimpleJson/README.txt new file mode 100644 index 0000000000..5e58eb7106 --- /dev/null +++ b/src/JSInterop/src/Microsoft.JSInterop/Json/SimpleJson/README.txt @@ -0,0 +1,24 @@ +SimpleJson is from https://github.com/facebook-csharp-sdk/simple-json + +LICENSE (from https://github.com/facebook-csharp-sdk/simple-json/blob/08b6871e8f63e866810d25e7a03c48502c9a234b/LICENSE.txt): +===== +Copyright (c) 2011, The Outercurve Foundation + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. \ No newline at end of file diff --git a/src/JSInterop/src/Microsoft.JSInterop/Json/SimpleJson/SimpleJson.cs b/src/JSInterop/src/Microsoft.JSInterop/Json/SimpleJson/SimpleJson.cs new file mode 100644 index 0000000000..d12c6fae30 --- /dev/null +++ b/src/JSInterop/src/Microsoft.JSInterop/Json/SimpleJson/SimpleJson.cs @@ -0,0 +1,2201 @@ +//----------------------------------------------------------------------- +// +// Copyright (c) 2011, The Outercurve Foundation. +// +// Licensed under the MIT License (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// http://www.opensource.org/licenses/mit-license.php +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +// Nathan Totten (ntotten.com), Jim Zimmerman (jimzimmerman.com) and Prabir Shrestha (prabir.me) +// https://github.com/facebook-csharp-sdk/simple-json +//----------------------------------------------------------------------- + +// VERSION: + +// NOTE: uncomment the following line to make SimpleJson class internal. +#define SIMPLE_JSON_INTERNAL + +// NOTE: uncomment the following line to make JsonArray and JsonObject class internal. +#define SIMPLE_JSON_OBJARRAYINTERNAL + +// NOTE: uncomment the following line to enable dynamic support. +//#define SIMPLE_JSON_DYNAMIC + +// NOTE: uncomment the following line to enable DataContract support. +//#define SIMPLE_JSON_DATACONTRACT + +// NOTE: uncomment the following line to enable IReadOnlyCollection and IReadOnlyList support. +//#define SIMPLE_JSON_READONLY_COLLECTIONS + +// NOTE: uncomment the following line to disable linq expressions/compiled lambda (better performance) instead of method.invoke(). +// define if you are using .net framework <= 3.0 or < WP7.5 +#define SIMPLE_JSON_NO_LINQ_EXPRESSION + +// NOTE: uncomment the following line if you are compiling under Window Metro style application/library. +// usually already defined in properties +//#define NETFX_CORE; + +// If you are targetting WinStore, WP8 and NET4.5+ PCL make sure to #define SIMPLE_JSON_TYPEINFO; + +// original json parsing code from http://techblog.procurios.nl/k/618/news/view/14605/14863/How-do-I-write-my-own-parser-for-JSON.html + +#if NETFX_CORE +#define SIMPLE_JSON_TYPEINFO +#endif + +using System; +using System.CodeDom.Compiler; +using System.Collections; +using System.Collections.Generic; +#if !SIMPLE_JSON_NO_LINQ_EXPRESSION +using System.Linq.Expressions; +#endif +using System.ComponentModel; +using System.Diagnostics.CodeAnalysis; +#if SIMPLE_JSON_DYNAMIC +using System.Dynamic; +#endif +using System.Globalization; +using System.Reflection; +using System.Runtime.Serialization; +using System.Text; +using Microsoft.JSInterop; +using SimpleJson.Reflection; + +// ReSharper disable LoopCanBeConvertedToQuery +// ReSharper disable RedundantExplicitArrayCreation +// ReSharper disable SuggestUseVarKeywordEvident +namespace SimpleJson +{ + /// + /// Represents the json array. + /// + [GeneratedCode("simple-json", "1.0.0")] + [EditorBrowsable(EditorBrowsableState.Never)] + [SuppressMessage("Microsoft.Naming", "CA1710:IdentifiersShouldHaveCorrectSuffix")] +#if SIMPLE_JSON_OBJARRAYINTERNAL + internal +#else + public +#endif + class JsonArray : List + { + /// + /// Initializes a new instance of the class. + /// + public JsonArray() { } + + /// + /// Initializes a new instance of the class. + /// + /// The capacity of the json array. + public JsonArray(int capacity) : base(capacity) { } + + /// + /// The json representation of the array. + /// + /// The json representation of the array. + public override string ToString() + { + return SimpleJson.SerializeObject(this) ?? string.Empty; + } + } + + /// + /// Represents the json object. + /// + [GeneratedCode("simple-json", "1.0.0")] + [EditorBrowsable(EditorBrowsableState.Never)] + [SuppressMessage("Microsoft.Naming", "CA1710:IdentifiersShouldHaveCorrectSuffix")] +#if SIMPLE_JSON_OBJARRAYINTERNAL + internal +#else + public +#endif + class JsonObject : +#if SIMPLE_JSON_DYNAMIC + DynamicObject, +#endif + IDictionary + { + /// + /// The internal member dictionary. + /// + private readonly Dictionary _members; + + /// + /// Initializes a new instance of . + /// + public JsonObject() + { + _members = new Dictionary(); + } + + /// + /// Initializes a new instance of . + /// + /// The implementation to use when comparing keys, or null to use the default for the type of the key. + public JsonObject(IEqualityComparer comparer) + { + _members = new Dictionary(comparer); + } + + /// + /// Gets the at the specified index. + /// + /// + public object this[int index] + { + get { return GetAtIndex(_members, index); } + } + + internal static object GetAtIndex(IDictionary obj, int index) + { + if (obj == null) + throw new ArgumentNullException("obj"); + if (index >= obj.Count) + throw new ArgumentOutOfRangeException("index"); + int i = 0; + foreach (KeyValuePair o in obj) + if (i++ == index) return o.Value; + return null; + } + + /// + /// Adds the specified key. + /// + /// The key. + /// The value. + public void Add(string key, object value) + { + _members.Add(key, value); + } + + /// + /// Determines whether the specified key contains key. + /// + /// The key. + /// + /// true if the specified key contains key; otherwise, false. + /// + public bool ContainsKey(string key) + { + return _members.ContainsKey(key); + } + + /// + /// Gets the keys. + /// + /// The keys. + public ICollection Keys + { + get { return _members.Keys; } + } + + /// + /// Removes the specified key. + /// + /// The key. + /// + public bool Remove(string key) + { + return _members.Remove(key); + } + + /// + /// Tries the get value. + /// + /// The key. + /// The value. + /// + public bool TryGetValue(string key, out object value) + { + return _members.TryGetValue(key, out value); + } + + /// + /// Gets the values. + /// + /// The values. + public ICollection Values + { + get { return _members.Values; } + } + + /// + /// Gets or sets the with the specified key. + /// + /// + public object this[string key] + { + get { return _members[key]; } + set { _members[key] = value; } + } + + /// + /// Adds the specified item. + /// + /// The item. + public void Add(KeyValuePair item) + { + _members.Add(item.Key, item.Value); + } + + /// + /// Clears this instance. + /// + public void Clear() + { + _members.Clear(); + } + + /// + /// Determines whether [contains] [the specified item]. + /// + /// The item. + /// + /// true if [contains] [the specified item]; otherwise, false. + /// + public bool Contains(KeyValuePair item) + { + return _members.ContainsKey(item.Key) && _members[item.Key] == item.Value; + } + + /// + /// Copies to. + /// + /// The array. + /// Index of the array. + public void CopyTo(KeyValuePair[] array, int arrayIndex) + { + if (array == null) throw new ArgumentNullException("array"); + int num = Count; + foreach (KeyValuePair kvp in this) + { + array[arrayIndex++] = kvp; + if (--num <= 0) + return; + } + } + + /// + /// Gets the count. + /// + /// The count. + public int Count + { + get { return _members.Count; } + } + + /// + /// Gets a value indicating whether this instance is read only. + /// + /// + /// true if this instance is read only; otherwise, false. + /// + public bool IsReadOnly + { + get { return false; } + } + + /// + /// Removes the specified item. + /// + /// The item. + /// + public bool Remove(KeyValuePair item) + { + return _members.Remove(item.Key); + } + + /// + /// Gets the enumerator. + /// + /// + public IEnumerator> GetEnumerator() + { + return _members.GetEnumerator(); + } + + /// + /// Returns an enumerator that iterates through a collection. + /// + /// + /// An object that can be used to iterate through the collection. + /// + IEnumerator IEnumerable.GetEnumerator() + { + return _members.GetEnumerator(); + } + + /// + /// Returns a json that represents the current . + /// + /// + /// A json that represents the current . + /// + public override string ToString() + { + return SimpleJson.SerializeObject(this); + } + +#if SIMPLE_JSON_DYNAMIC + /// + /// Provides implementation for type conversion operations. Classes derived from the class can override this method to specify dynamic behavior for operations that convert an object from one type to another. + /// + /// Provides information about the conversion operation. The binder.Type property provides the type to which the object must be converted. For example, for the statement (String)sampleObject in C# (CType(sampleObject, Type) in Visual Basic), where sampleObject is an instance of the class derived from the class, binder.Type returns the type. The binder.Explicit property provides information about the kind of conversion that occurs. It returns true for explicit conversion and false for implicit conversion. + /// The result of the type conversion operation. + /// + /// Alwasy returns true. + /// + public override bool TryConvert(ConvertBinder binder, out object result) + { + // + if (binder == null) + throw new ArgumentNullException("binder"); + // + Type targetType = binder.Type; + + if ((targetType == typeof(IEnumerable)) || + (targetType == typeof(IEnumerable>)) || + (targetType == typeof(IDictionary)) || + (targetType == typeof(IDictionary))) + { + result = this; + return true; + } + + return base.TryConvert(binder, out result); + } + + /// + /// Provides the implementation for operations that delete an object member. This method is not intended for use in C# or Visual Basic. + /// + /// Provides information about the deletion. + /// + /// Alwasy returns true. + /// + public override bool TryDeleteMember(DeleteMemberBinder binder) + { + // + if (binder == null) + throw new ArgumentNullException("binder"); + // + return _members.Remove(binder.Name); + } + + /// + /// Provides the implementation for operations that get a value by index. Classes derived from the class can override this method to specify dynamic behavior for indexing operations. + /// + /// Provides information about the operation. + /// The indexes that are used in the operation. For example, for the sampleObject[3] operation in C# (sampleObject(3) in Visual Basic), where sampleObject is derived from the DynamicObject class, is equal to 3. + /// The result of the index operation. + /// + /// Alwasy returns true. + /// + public override bool TryGetIndex(GetIndexBinder binder, object[] indexes, out object result) + { + if (indexes == null) throw new ArgumentNullException("indexes"); + if (indexes.Length == 1) + { + result = ((IDictionary)this)[(string)indexes[0]]; + return true; + } + result = null; + return true; + } + + /// + /// Provides the implementation for operations that get member values. Classes derived from the class can override this method to specify dynamic behavior for operations such as getting a value for a property. + /// + /// Provides information about the object that called the dynamic operation. The binder.Name property provides the name of the member on which the dynamic operation is performed. For example, for the Console.WriteLine(sampleObject.SampleProperty) statement, where sampleObject is an instance of the class derived from the class, binder.Name returns "SampleProperty". The binder.IgnoreCase property specifies whether the member name is case-sensitive. + /// The result of the get operation. For example, if the method is called for a property, you can assign the property value to . + /// + /// Alwasy returns true. + /// + public override bool TryGetMember(GetMemberBinder binder, out object result) + { + object value; + if (_members.TryGetValue(binder.Name, out value)) + { + result = value; + return true; + } + result = null; + return true; + } + + /// + /// Provides the implementation for operations that set a value by index. Classes derived from the class can override this method to specify dynamic behavior for operations that access objects by a specified index. + /// + /// Provides information about the operation. + /// The indexes that are used in the operation. For example, for the sampleObject[3] = 10 operation in C# (sampleObject(3) = 10 in Visual Basic), where sampleObject is derived from the class, is equal to 3. + /// The value to set to the object that has the specified index. For example, for the sampleObject[3] = 10 operation in C# (sampleObject(3) = 10 in Visual Basic), where sampleObject is derived from the class, is equal to 10. + /// + /// true if the operation is successful; otherwise, false. If this method returns false, the run-time binder of the language determines the behavior. (In most cases, a language-specific run-time exception is thrown. + /// + public override bool TrySetIndex(SetIndexBinder binder, object[] indexes, object value) + { + if (indexes == null) throw new ArgumentNullException("indexes"); + if (indexes.Length == 1) + { + ((IDictionary)this)[(string)indexes[0]] = value; + return true; + } + return base.TrySetIndex(binder, indexes, value); + } + + /// + /// Provides the implementation for operations that set member values. Classes derived from the class can override this method to specify dynamic behavior for operations such as setting a value for a property. + /// + /// Provides information about the object that called the dynamic operation. The binder.Name property provides the name of the member to which the value is being assigned. For example, for the statement sampleObject.SampleProperty = "Test", where sampleObject is an instance of the class derived from the class, binder.Name returns "SampleProperty". The binder.IgnoreCase property specifies whether the member name is case-sensitive. + /// The value to set to the member. For example, for sampleObject.SampleProperty = "Test", where sampleObject is an instance of the class derived from the class, the is "Test". + /// + /// true if the operation is successful; otherwise, false. If this method returns false, the run-time binder of the language determines the behavior. (In most cases, a language-specific run-time exception is thrown.) + /// + public override bool TrySetMember(SetMemberBinder binder, object value) + { + // + if (binder == null) + throw new ArgumentNullException("binder"); + // + _members[binder.Name] = value; + return true; + } + + /// + /// Returns the enumeration of all dynamic member names. + /// + /// + /// A sequence that contains dynamic member names. + /// + public override IEnumerable GetDynamicMemberNames() + { + foreach (var key in Keys) + yield return key; + } +#endif + } +} + +namespace SimpleJson +{ + /// + /// This class encodes and decodes JSON strings. + /// Spec. details, see http://www.json.org/ + /// + /// JSON uses Arrays and Objects. These correspond here to the datatypes JsonArray(IList<object>) and JsonObject(IDictionary<string,object>). + /// All numbers are parsed to doubles. + /// + [GeneratedCode("simple-json", "1.0.0")] +#if SIMPLE_JSON_INTERNAL + internal +#else + public +#endif + static class SimpleJson + { + private const int TOKEN_NONE = 0; + private const int TOKEN_CURLY_OPEN = 1; + private const int TOKEN_CURLY_CLOSE = 2; + private const int TOKEN_SQUARED_OPEN = 3; + private const int TOKEN_SQUARED_CLOSE = 4; + private const int TOKEN_COLON = 5; + private const int TOKEN_COMMA = 6; + private const int TOKEN_STRING = 7; + private const int TOKEN_NUMBER = 8; + private const int TOKEN_TRUE = 9; + private const int TOKEN_FALSE = 10; + private const int TOKEN_NULL = 11; + private const int BUILDER_CAPACITY = 2000; + + private static readonly char[] EscapeTable; + private static readonly char[] EscapeCharacters = new char[] { '"', '\\', '\b', '\f', '\n', '\r', '\t' }; + private static readonly string EscapeCharactersString = new string(EscapeCharacters); + + static SimpleJson() + { + EscapeTable = new char[93]; + EscapeTable['"'] = '"'; + EscapeTable['\\'] = '\\'; + EscapeTable['\b'] = 'b'; + EscapeTable['\f'] = 'f'; + EscapeTable['\n'] = 'n'; + EscapeTable['\r'] = 'r'; + EscapeTable['\t'] = 't'; + } + + /// + /// Parses the string json into a value + /// + /// A JSON string. + /// An IList<object>, a IDictionary<string,object>, a double, a string, null, true, or false + public static object DeserializeObject(string json) + { + object obj; + if (TryDeserializeObject(json, out obj)) + return obj; + throw new SerializationException("Invalid JSON string"); + } + + /// + /// Try parsing the json string into a value. + /// + /// + /// A JSON string. + /// + /// + /// The object. + /// + /// + /// Returns true if successful otherwise false. + /// + [SuppressMessage("Microsoft.Design", "CA1007:UseGenericsWhereAppropriate", Justification="Need to support .NET 2")] + public static bool TryDeserializeObject(string json, out object obj) + { + bool success = true; + if (json != null) + { + char[] charArray = json.ToCharArray(); + int index = 0; + obj = ParseValue(charArray, ref index, ref success); + } + else + obj = null; + + return success; + } + + public static object DeserializeObject(string json, Type type, IJsonSerializerStrategy jsonSerializerStrategy) + { + object jsonObject = DeserializeObject(json); + return type == null || jsonObject != null && ReflectionUtils.IsAssignableFrom(jsonObject.GetType(), type) + ? jsonObject + : (jsonSerializerStrategy ?? CurrentJsonSerializerStrategy).DeserializeObject(jsonObject, type); + } + + public static object DeserializeObject(string json, Type type) + { + return DeserializeObject(json, type, null); + } + + public static T DeserializeObject(string json, IJsonSerializerStrategy jsonSerializerStrategy) + { + return (T)DeserializeObject(json, typeof(T), jsonSerializerStrategy); + } + + public static T DeserializeObject(string json) + { + return (T)DeserializeObject(json, typeof(T), null); + } + + /// + /// Converts a IDictionary<string,object> / IList<object> object into a JSON string + /// + /// A IDictionary<string,object> / IList<object> + /// Serializer strategy to use + /// A JSON encoded string, or null if object 'json' is not serializable + public static string SerializeObject(object json, IJsonSerializerStrategy jsonSerializerStrategy) + { + StringBuilder builder = new StringBuilder(BUILDER_CAPACITY); + bool success = SerializeValue(jsonSerializerStrategy, json, builder); + return (success ? builder.ToString() : null); + } + + public static string SerializeObject(object json) + { + return SerializeObject(json, CurrentJsonSerializerStrategy); + } + + public static string EscapeToJavascriptString(string jsonString) + { + if (string.IsNullOrEmpty(jsonString)) + return jsonString; + + StringBuilder sb = new StringBuilder(); + char c; + + for (int i = 0; i < jsonString.Length; ) + { + c = jsonString[i++]; + + if (c == '\\') + { + int remainingLength = jsonString.Length - i; + if (remainingLength >= 2) + { + char lookahead = jsonString[i]; + if (lookahead == '\\') + { + sb.Append('\\'); + ++i; + } + else if (lookahead == '"') + { + sb.Append("\""); + ++i; + } + else if (lookahead == 't') + { + sb.Append('\t'); + ++i; + } + else if (lookahead == 'b') + { + sb.Append('\b'); + ++i; + } + else if (lookahead == 'n') + { + sb.Append('\n'); + ++i; + } + else if (lookahead == 'r') + { + sb.Append('\r'); + ++i; + } + } + } + else + { + sb.Append(c); + } + } + return sb.ToString(); + } + + static IDictionary ParseObject(char[] json, ref int index, ref bool success) + { + IDictionary table = new JsonObject(); + int token; + + // { + NextToken(json, ref index); + + bool done = false; + while (!done) + { + token = LookAhead(json, index); + if (token == TOKEN_NONE) + { + success = false; + return null; + } + else if (token == TOKEN_COMMA) + NextToken(json, ref index); + else if (token == TOKEN_CURLY_CLOSE) + { + NextToken(json, ref index); + return table; + } + else + { + // name + string name = ParseString(json, ref index, ref success); + if (!success) + { + success = false; + return null; + } + // : + token = NextToken(json, ref index); + if (token != TOKEN_COLON) + { + success = false; + return null; + } + // value + object value = ParseValue(json, ref index, ref success); + if (!success) + { + success = false; + return null; + } + table[name] = value; + } + } + return table; + } + + static JsonArray ParseArray(char[] json, ref int index, ref bool success) + { + JsonArray array = new JsonArray(); + + // [ + NextToken(json, ref index); + + bool done = false; + while (!done) + { + int token = LookAhead(json, index); + if (token == TOKEN_NONE) + { + success = false; + return null; + } + else if (token == TOKEN_COMMA) + NextToken(json, ref index); + else if (token == TOKEN_SQUARED_CLOSE) + { + NextToken(json, ref index); + break; + } + else + { + object value = ParseValue(json, ref index, ref success); + if (!success) + return null; + array.Add(value); + } + } + return array; + } + + static object ParseValue(char[] json, ref int index, ref bool success) + { + switch (LookAhead(json, index)) + { + case TOKEN_STRING: + return ParseString(json, ref index, ref success); + case TOKEN_NUMBER: + return ParseNumber(json, ref index, ref success); + case TOKEN_CURLY_OPEN: + return ParseObject(json, ref index, ref success); + case TOKEN_SQUARED_OPEN: + return ParseArray(json, ref index, ref success); + case TOKEN_TRUE: + NextToken(json, ref index); + return true; + case TOKEN_FALSE: + NextToken(json, ref index); + return false; + case TOKEN_NULL: + NextToken(json, ref index); + return null; + case TOKEN_NONE: + break; + } + success = false; + return null; + } + + static string ParseString(char[] json, ref int index, ref bool success) + { + StringBuilder s = new StringBuilder(BUILDER_CAPACITY); + char c; + + EatWhitespace(json, ref index); + + // " + c = json[index++]; + bool complete = false; + while (!complete) + { + if (index == json.Length) + break; + + c = json[index++]; + if (c == '"') + { + complete = true; + break; + } + else if (c == '\\') + { + if (index == json.Length) + break; + c = json[index++]; + if (c == '"') + s.Append('"'); + else if (c == '\\') + s.Append('\\'); + else if (c == '/') + s.Append('/'); + else if (c == 'b') + s.Append('\b'); + else if (c == 'f') + s.Append('\f'); + else if (c == 'n') + s.Append('\n'); + else if (c == 'r') + s.Append('\r'); + else if (c == 't') + s.Append('\t'); + else if (c == 'u') + { + int remainingLength = json.Length - index; + if (remainingLength >= 4) + { + // parse the 32 bit hex into an integer codepoint + uint codePoint; + if (!(success = UInt32.TryParse(new string(json, index, 4), NumberStyles.HexNumber, CultureInfo.InvariantCulture, out codePoint))) + return ""; + + // convert the integer codepoint to a unicode char and add to string + if (0xD800 <= codePoint && codePoint <= 0xDBFF) // if high surrogate + { + index += 4; // skip 4 chars + remainingLength = json.Length - index; + if (remainingLength >= 6) + { + uint lowCodePoint; + if (new string(json, index, 2) == "\\u" && UInt32.TryParse(new string(json, index + 2, 4), NumberStyles.HexNumber, CultureInfo.InvariantCulture, out lowCodePoint)) + { + if (0xDC00 <= lowCodePoint && lowCodePoint <= 0xDFFF) // if low surrogate + { + s.Append((char)codePoint); + s.Append((char)lowCodePoint); + index += 6; // skip 6 chars + continue; + } + } + } + success = false; // invalid surrogate pair + return ""; + } + s.Append(ConvertFromUtf32((int)codePoint)); + // skip 4 chars + index += 4; + } + else + break; + } + } + else + s.Append(c); + } + if (!complete) + { + success = false; + return null; + } + return s.ToString(); + } + + private static string ConvertFromUtf32(int utf32) + { + // http://www.java2s.com/Open-Source/CSharp/2.6.4-mono-.net-core/System/System/Char.cs.htm + if (utf32 < 0 || utf32 > 0x10FFFF) + throw new ArgumentOutOfRangeException("utf32", "The argument must be from 0 to 0x10FFFF."); + if (0xD800 <= utf32 && utf32 <= 0xDFFF) + throw new ArgumentOutOfRangeException("utf32", "The argument must not be in surrogate pair range."); + if (utf32 < 0x10000) + return new string((char)utf32, 1); + utf32 -= 0x10000; + return new string(new char[] { (char)((utf32 >> 10) + 0xD800), (char)(utf32 % 0x0400 + 0xDC00) }); + } + + static object ParseNumber(char[] json, ref int index, ref bool success) + { + EatWhitespace(json, ref index); + int lastIndex = GetLastIndexOfNumber(json, index); + int charLength = (lastIndex - index) + 1; + object returnNumber; + string str = new string(json, index, charLength); + if (str.IndexOf(".", StringComparison.OrdinalIgnoreCase) != -1 || str.IndexOf("e", StringComparison.OrdinalIgnoreCase) != -1) + { + double number; + success = double.TryParse(new string(json, index, charLength), NumberStyles.Any, CultureInfo.InvariantCulture, out number); + returnNumber = number; + } + else + { + long number; + success = long.TryParse(new string(json, index, charLength), NumberStyles.Any, CultureInfo.InvariantCulture, out number); + returnNumber = number; + } + index = lastIndex + 1; + return returnNumber; + } + + static int GetLastIndexOfNumber(char[] json, int index) + { + int lastIndex; + for (lastIndex = index; lastIndex < json.Length; lastIndex++) + if ("0123456789+-.eE".IndexOf(json[lastIndex]) == -1) break; + return lastIndex - 1; + } + + static void EatWhitespace(char[] json, ref int index) + { + for (; index < json.Length; index++) { + switch (json[index]) { + case ' ': + case '\t': + case '\n': + case '\r': + case '\b': + case '\f': + break; + default: + return; + } + } + } + + static int LookAhead(char[] json, int index) + { + int saveIndex = index; + return NextToken(json, ref saveIndex); + } + + [SuppressMessage("Microsoft.Maintainability", "CA1502:AvoidExcessiveComplexity")] + static int NextToken(char[] json, ref int index) + { + EatWhitespace(json, ref index); + if (index == json.Length) + return TOKEN_NONE; + char c = json[index]; + index++; + switch (c) + { + case '{': + return TOKEN_CURLY_OPEN; + case '}': + return TOKEN_CURLY_CLOSE; + case '[': + return TOKEN_SQUARED_OPEN; + case ']': + return TOKEN_SQUARED_CLOSE; + case ',': + return TOKEN_COMMA; + case '"': + return TOKEN_STRING; + case '0': + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + case '8': + case '9': + case '-': + return TOKEN_NUMBER; + case ':': + return TOKEN_COLON; + } + index--; + int remainingLength = json.Length - index; + // false + if (remainingLength >= 5) + { + if (json[index] == 'f' && json[index + 1] == 'a' && json[index + 2] == 'l' && json[index + 3] == 's' && json[index + 4] == 'e') + { + index += 5; + return TOKEN_FALSE; + } + } + // true + if (remainingLength >= 4) + { + if (json[index] == 't' && json[index + 1] == 'r' && json[index + 2] == 'u' && json[index + 3] == 'e') + { + index += 4; + return TOKEN_TRUE; + } + } + // null + if (remainingLength >= 4) + { + if (json[index] == 'n' && json[index + 1] == 'u' && json[index + 2] == 'l' && json[index + 3] == 'l') + { + index += 4; + return TOKEN_NULL; + } + } + return TOKEN_NONE; + } + + static bool SerializeValue(IJsonSerializerStrategy jsonSerializerStrategy, object value, StringBuilder builder) + { + bool success = true; + string stringValue = value as string; + if (stringValue != null) + success = SerializeString(stringValue, builder); + else + { + IDictionary dict = value as IDictionary; + if (dict != null) + { + success = SerializeObject(jsonSerializerStrategy, dict.Keys, dict.Values, builder); + } + else + { + IDictionary stringDictionary = value as IDictionary; + if (stringDictionary != null) + { + success = SerializeObject(jsonSerializerStrategy, stringDictionary.Keys, stringDictionary.Values, builder); + } + else + { + IEnumerable enumerableValue = value as IEnumerable; + if (enumerableValue != null) + success = SerializeArray(jsonSerializerStrategy, enumerableValue, builder); + else if (IsNumeric(value)) + success = SerializeNumber(value, builder); + else if (value is bool) + builder.Append((bool)value ? "true" : "false"); + else if (value == null) + builder.Append("null"); + else + { + object serializedObject; + success = jsonSerializerStrategy.TrySerializeNonPrimitiveObject(value, out serializedObject); + if (success) + SerializeValue(jsonSerializerStrategy, serializedObject, builder); + } + } + } + } + return success; + } + + static bool SerializeObject(IJsonSerializerStrategy jsonSerializerStrategy, IEnumerable keys, IEnumerable values, StringBuilder builder) + { + builder.Append("{"); + IEnumerator ke = keys.GetEnumerator(); + IEnumerator ve = values.GetEnumerator(); + bool first = true; + while (ke.MoveNext() && ve.MoveNext()) + { + object key = ke.Current; + object value = ve.Current; + if (!first) + builder.Append(","); + string stringKey = key as string; + if (stringKey != null) + SerializeString(stringKey, builder); + else + if (!SerializeValue(jsonSerializerStrategy, value, builder)) return false; + builder.Append(":"); + if (!SerializeValue(jsonSerializerStrategy, value, builder)) + return false; + first = false; + } + builder.Append("}"); + return true; + } + + static bool SerializeArray(IJsonSerializerStrategy jsonSerializerStrategy, IEnumerable anArray, StringBuilder builder) + { + builder.Append("["); + bool first = true; + foreach (object value in anArray) + { + if (!first) + builder.Append(","); + if (!SerializeValue(jsonSerializerStrategy, value, builder)) + return false; + first = false; + } + builder.Append("]"); + return true; + } + + static bool SerializeString(string aString, StringBuilder builder) + { + // Happy path if there's nothing to be escaped. IndexOfAny is highly optimized (and unmanaged) + if (aString.IndexOfAny(EscapeCharacters) == -1) + { + builder.Append('"'); + builder.Append(aString); + builder.Append('"'); + + return true; + } + + builder.Append('"'); + int safeCharacterCount = 0; + char[] charArray = aString.ToCharArray(); + + for (int i = 0; i < charArray.Length; i++) + { + char c = charArray[i]; + + // Non ascii characters are fine, buffer them up and send them to the builder + // in larger chunks if possible. The escape table is a 1:1 translation table + // with \0 [default(char)] denoting a safe character. + if (c >= EscapeTable.Length || EscapeTable[c] == default(char)) + { + safeCharacterCount++; + } + else + { + if (safeCharacterCount > 0) + { + builder.Append(charArray, i - safeCharacterCount, safeCharacterCount); + safeCharacterCount = 0; + } + + builder.Append('\\'); + builder.Append(EscapeTable[c]); + } + } + + if (safeCharacterCount > 0) + { + builder.Append(charArray, charArray.Length - safeCharacterCount, safeCharacterCount); + } + + builder.Append('"'); + return true; + } + + static bool SerializeNumber(object number, StringBuilder builder) + { + if (number is long) + builder.Append(((long)number).ToString(CultureInfo.InvariantCulture)); + else if (number is ulong) + builder.Append(((ulong)number).ToString(CultureInfo.InvariantCulture)); + else if (number is int) + builder.Append(((int)number).ToString(CultureInfo.InvariantCulture)); + else if (number is uint) + builder.Append(((uint)number).ToString(CultureInfo.InvariantCulture)); + else if (number is decimal) + builder.Append(((decimal)number).ToString(CultureInfo.InvariantCulture)); + else if (number is float) + builder.Append(((float)number).ToString(CultureInfo.InvariantCulture)); + else + builder.Append(Convert.ToDouble(number, CultureInfo.InvariantCulture).ToString("r", CultureInfo.InvariantCulture)); + return true; + } + + /// + /// Determines if a given object is numeric in any way + /// (can be integer, double, null, etc). + /// + static bool IsNumeric(object value) + { + if (value is sbyte) return true; + if (value is byte) return true; + if (value is short) return true; + if (value is ushort) return true; + if (value is int) return true; + if (value is uint) return true; + if (value is long) return true; + if (value is ulong) return true; + if (value is float) return true; + if (value is double) return true; + if (value is decimal) return true; + return false; + } + + private static IJsonSerializerStrategy _currentJsonSerializerStrategy; + public static IJsonSerializerStrategy CurrentJsonSerializerStrategy + { + get + { + return _currentJsonSerializerStrategy ?? + (_currentJsonSerializerStrategy = +#if SIMPLE_JSON_DATACONTRACT + DataContractJsonSerializerStrategy +#else + PocoJsonSerializerStrategy +#endif +); + } + set + { + _currentJsonSerializerStrategy = value; + } + } + + private static PocoJsonSerializerStrategy _pocoJsonSerializerStrategy; + [EditorBrowsable(EditorBrowsableState.Advanced)] + public static PocoJsonSerializerStrategy PocoJsonSerializerStrategy + { + get + { + return _pocoJsonSerializerStrategy ?? (_pocoJsonSerializerStrategy = new PocoJsonSerializerStrategy()); + } + } + +#if SIMPLE_JSON_DATACONTRACT + + private static DataContractJsonSerializerStrategy _dataContractJsonSerializerStrategy; + [System.ComponentModel.EditorBrowsable(EditorBrowsableState.Advanced)] + public static DataContractJsonSerializerStrategy DataContractJsonSerializerStrategy + { + get + { + return _dataContractJsonSerializerStrategy ?? (_dataContractJsonSerializerStrategy = new DataContractJsonSerializerStrategy()); + } + } + +#endif + } + + [GeneratedCode("simple-json", "1.0.0")] +#if SIMPLE_JSON_INTERNAL + internal +#else + public +#endif + interface IJsonSerializerStrategy + { + [SuppressMessage("Microsoft.Design", "CA1007:UseGenericsWhereAppropriate", Justification="Need to support .NET 2")] + bool TrySerializeNonPrimitiveObject(object input, out object output); + object DeserializeObject(object value, Type type); + } + + [GeneratedCode("simple-json", "1.0.0")] +#if SIMPLE_JSON_INTERNAL + internal +#else + public +#endif + class PocoJsonSerializerStrategy : IJsonSerializerStrategy + { + internal IDictionary ConstructorCache; + internal IDictionary> GetCache; + internal IDictionary>> SetCache; + + internal static readonly Type[] EmptyTypes = new Type[0]; + internal static readonly Type[] ArrayConstructorParameterTypes = new Type[] { typeof(int) }; + + private static readonly string[] Iso8601Format = new string[] + { + @"yyyy-MM-dd\THH:mm:ss.FFFFFFF\Z", + @"yyyy-MM-dd\THH:mm:ss\Z", + @"yyyy-MM-dd\THH:mm:ssK" + }; + + public PocoJsonSerializerStrategy() + { + ConstructorCache = new ReflectionUtils.ThreadSafeDictionary(ConstructorDelegateFactory); + GetCache = new ReflectionUtils.ThreadSafeDictionary>(GetterValueFactory); + SetCache = new ReflectionUtils.ThreadSafeDictionary>>(SetterValueFactory); + } + + protected virtual string MapClrMemberNameToJsonFieldName(string clrPropertyName) + { + return CamelCase.MemberNameToCamelCase(clrPropertyName); + } + + internal virtual ReflectionUtils.ConstructorDelegate ConstructorDelegateFactory(Type key) + { + // We need List(int) constructor so that DeserializeObject method will work for generating IList-declared values + var needsCapacityArgument = key.IsArray || key.IsConstructedGenericType && key.GetGenericTypeDefinition() == typeof(List<>); + return ReflectionUtils.GetConstructor(key, needsCapacityArgument ? ArrayConstructorParameterTypes : EmptyTypes); + } + + internal virtual IDictionary GetterValueFactory(Type type) + { + IDictionary result = new Dictionary(); + foreach (PropertyInfo propertyInfo in ReflectionUtils.GetProperties(type)) + { + if (propertyInfo.CanRead) + { + MethodInfo getMethod = ReflectionUtils.GetGetterMethodInfo(propertyInfo); + if (getMethod.IsStatic || !getMethod.IsPublic) + continue; + result[MapClrMemberNameToJsonFieldName(propertyInfo.Name)] = ReflectionUtils.GetGetMethod(propertyInfo); + } + } + foreach (FieldInfo fieldInfo in ReflectionUtils.GetFields(type)) + { + if (fieldInfo.IsStatic || !fieldInfo.IsPublic) + continue; + result[MapClrMemberNameToJsonFieldName(fieldInfo.Name)] = ReflectionUtils.GetGetMethod(fieldInfo); + } + return result; + } + + internal virtual IDictionary> SetterValueFactory(Type type) + { + // BLAZOR-SPECIFIC MODIFICATION FROM STOCK SIMPLEJSON: + // + // For incoming keys we match case-insensitively. But if two .NET properties differ only by case, + // it's ambiguous which should be used: the one that matches the incoming JSON exactly, or the + // one that uses 'correct' PascalCase corresponding to the incoming camelCase? What if neither + // meets these descriptions? + // + // To resolve this: + // - If multiple public properties differ only by case, we throw + // - If multiple public fields differ only by case, we throw + // - If there's a public property and a public field that differ only by case, we prefer the property + // This unambiguously selects one member, and that's what we'll use. + + IDictionary> result = new Dictionary>(StringComparer.OrdinalIgnoreCase); + foreach (PropertyInfo propertyInfo in ReflectionUtils.GetProperties(type)) + { + if (propertyInfo.CanWrite) + { + MethodInfo setMethod = ReflectionUtils.GetSetterMethodInfo(propertyInfo); + if (setMethod.IsStatic) + continue; + if (result.ContainsKey(propertyInfo.Name)) + { + throw new InvalidOperationException($"The type '{type.FullName}' contains multiple public properties with names case-insensitively matching '{propertyInfo.Name.ToLowerInvariant()}'. Such types cannot be used for JSON deserialization."); + } + result[propertyInfo.Name] = new KeyValuePair(propertyInfo.PropertyType, ReflectionUtils.GetSetMethod(propertyInfo)); + } + } + + IDictionary> fieldResult = new Dictionary>(StringComparer.OrdinalIgnoreCase); + foreach (FieldInfo fieldInfo in ReflectionUtils.GetFields(type)) + { + if (fieldInfo.IsInitOnly || fieldInfo.IsStatic || !fieldInfo.IsPublic) + continue; + if (fieldResult.ContainsKey(fieldInfo.Name)) + { + throw new InvalidOperationException($"The type '{type.FullName}' contains multiple public fields with names case-insensitively matching '{fieldInfo.Name.ToLowerInvariant()}'. Such types cannot be used for JSON deserialization."); + } + fieldResult[fieldInfo.Name] = new KeyValuePair(fieldInfo.FieldType, ReflectionUtils.GetSetMethod(fieldInfo)); + if (!result.ContainsKey(fieldInfo.Name)) + { + result[fieldInfo.Name] = fieldResult[fieldInfo.Name]; + } + } + + return result; + } + + public virtual bool TrySerializeNonPrimitiveObject(object input, out object output) + { + return TrySerializeKnownTypes(input, out output) || TrySerializeUnknownTypes(input, out output); + } + + [SuppressMessage("Microsoft.Maintainability", "CA1502:AvoidExcessiveComplexity")] + public virtual object DeserializeObject(object value, Type type) + { + if (type == null) throw new ArgumentNullException("type"); + string str = value as string; + + if (type == typeof (Guid) && string.IsNullOrEmpty(str)) + return default(Guid); + + if (type.IsEnum) + { + type = type.GetEnumUnderlyingType(); + } + + if (value == null) + return null; + + object obj = null; + + if (str != null) + { + if (str.Length != 0) // We know it can't be null now. + { + if (type == typeof(TimeSpan) || (ReflectionUtils.IsNullableType(type) && Nullable.GetUnderlyingType(type) == typeof(TimeSpan))) + return TimeSpan.ParseExact(str, "c", CultureInfo.InvariantCulture); + if (type == typeof(DateTime) || (ReflectionUtils.IsNullableType(type) && Nullable.GetUnderlyingType(type) == typeof(DateTime))) + return DateTime.TryParseExact(str, Iso8601Format, CultureInfo.InvariantCulture, DateTimeStyles.AssumeUniversal | DateTimeStyles.AdjustToUniversal, out var result) + ? result : DateTime.Parse(str, CultureInfo.InvariantCulture, DateTimeStyles.RoundtripKind); + if (type == typeof(DateTimeOffset) || (ReflectionUtils.IsNullableType(type) && Nullable.GetUnderlyingType(type) == typeof(DateTimeOffset))) + return DateTimeOffset.TryParseExact(str, Iso8601Format, CultureInfo.InvariantCulture, DateTimeStyles.AssumeUniversal | DateTimeStyles.AdjustToUniversal, out var result) + ? result : DateTimeOffset.Parse(str, CultureInfo.InvariantCulture, DateTimeStyles.RoundtripKind); + if (type == typeof(Guid) || (ReflectionUtils.IsNullableType(type) && Nullable.GetUnderlyingType(type) == typeof(Guid))) + return new Guid(str); + if (type == typeof(Uri)) + { + bool isValid = Uri.IsWellFormedUriString(str, UriKind.RelativeOrAbsolute); + + Uri result; + if (isValid && Uri.TryCreate(str, UriKind.RelativeOrAbsolute, out result)) + return result; + + return null; + } + + if (type == typeof(string)) + return str; + + return Convert.ChangeType(str, type, CultureInfo.InvariantCulture); + } + else + { + if (type == typeof(Guid)) + obj = default(Guid); + else if (ReflectionUtils.IsNullableType(type) && Nullable.GetUnderlyingType(type) == typeof(Guid)) + obj = null; + else + obj = str; + } + // Empty string case + if (!ReflectionUtils.IsNullableType(type) && Nullable.GetUnderlyingType(type) == typeof(Guid)) + return str; + } + else if (value is bool) + return value; + + bool valueIsLong = value is long; + bool valueIsDouble = value is double; + if ((valueIsLong && type == typeof(long)) || (valueIsDouble && type == typeof(double))) + return value; + if ((valueIsDouble && type != typeof(double)) || (valueIsLong && type != typeof(long))) + { + obj = type == typeof(int) || type == typeof(long) || type == typeof(double) || type == typeof(float) || type == typeof(bool) || type == typeof(decimal) || type == typeof(byte) || type == typeof(short) + ? Convert.ChangeType(value, type, CultureInfo.InvariantCulture) + : value; + } + else + { + IDictionary objects = value as IDictionary; + if (objects != null) + { + IDictionary jsonObject = objects; + + if (ReflectionUtils.IsTypeDictionary(type)) + { + // if dictionary then + Type[] types = ReflectionUtils.GetGenericTypeArguments(type); + Type keyType = types[0]; + Type valueType = types[1]; + + Type genericType = typeof(Dictionary<,>).MakeGenericType(keyType, valueType); + + IDictionary dict = (IDictionary)ConstructorCache[genericType](); + + foreach (KeyValuePair kvp in jsonObject) + dict.Add(kvp.Key, DeserializeObject(kvp.Value, valueType)); + + obj = dict; + } + else + { + if (type == typeof(object)) + obj = value; + else + { + var constructorDelegate = ConstructorCache[type] + ?? throw new InvalidOperationException($"Cannot deserialize JSON into type '{type.FullName}' because it does not have a public parameterless constructor."); + obj = constructorDelegate(); + + var setterCache = SetCache[type]; + foreach (var jsonKeyValuePair in jsonObject) + { + if (setterCache.TryGetValue(jsonKeyValuePair.Key, out var setter)) + { + var jsonValue = DeserializeObject(jsonKeyValuePair.Value, setter.Key); + setter.Value(obj, jsonValue); + } + } + } + } + } + else + { + IList valueAsList = value as IList; + if (valueAsList != null) + { + IList jsonObject = valueAsList; + IList list = null; + + if (type.IsArray) + { + list = (IList)ConstructorCache[type](jsonObject.Count); + int i = 0; + foreach (object o in jsonObject) + list[i++] = DeserializeObject(o, type.GetElementType()); + } + else if (ReflectionUtils.IsTypeGenericCollectionInterface(type) || ReflectionUtils.IsAssignableFrom(typeof(IList), type)) + { + Type innerType = ReflectionUtils.GetGenericListElementType(type); + list = (IList)(ConstructorCache[type] ?? ConstructorCache[typeof(List<>).MakeGenericType(innerType)])(jsonObject.Count); + foreach (object o in jsonObject) + list.Add(DeserializeObject(o, innerType)); + } + obj = list; + } + } + return obj; + } + if (ReflectionUtils.IsNullableType(type)) + { + // For nullable enums serialized as numbers + if (Nullable.GetUnderlyingType(type).IsEnum) + { + return Enum.ToObject(Nullable.GetUnderlyingType(type), value); + } + + return ReflectionUtils.ToNullableType(obj, type); + } + + return obj; + } + + protected virtual object SerializeEnum(Enum p) + { + return Convert.ToDouble(p, CultureInfo.InvariantCulture); + } + + [SuppressMessage("Microsoft.Design", "CA1007:UseGenericsWhereAppropriate", Justification="Need to support .NET 2")] + protected virtual bool TrySerializeKnownTypes(object input, out object output) + { + bool returnValue = true; + if (input is DateTime) + output = ((DateTime)input).ToUniversalTime().ToString(Iso8601Format[0], CultureInfo.InvariantCulture); + else if (input is DateTimeOffset) + output = ((DateTimeOffset)input).ToString("o"); + else if (input is Guid) + output = ((Guid)input).ToString("D"); + else if (input is Uri) + output = input.ToString(); + else if (input is TimeSpan) + output = ((TimeSpan)input).ToString("c"); + else + { + Enum inputEnum = input as Enum; + if (inputEnum != null) + output = SerializeEnum(inputEnum); + else + { + returnValue = false; + output = null; + } + } + return returnValue; + } + [SuppressMessage("Microsoft.Design", "CA1007:UseGenericsWhereAppropriate", Justification="Need to support .NET 2")] + protected virtual bool TrySerializeUnknownTypes(object input, out object output) + { + if (input == null) throw new ArgumentNullException("input"); + output = null; + Type type = input.GetType(); + if (type.FullName == null) + return false; + IDictionary obj = new JsonObject(); + IDictionary getters = GetCache[type]; + foreach (KeyValuePair getter in getters) + { + if (getter.Value != null) + obj.Add(MapClrMemberNameToJsonFieldName(getter.Key), getter.Value(input)); + } + output = obj; + return true; + } + } + +#if SIMPLE_JSON_DATACONTRACT + [GeneratedCode("simple-json", "1.0.0")] +#if SIMPLE_JSON_INTERNAL + internal +#else + public +#endif + class DataContractJsonSerializerStrategy : PocoJsonSerializerStrategy + { + public DataContractJsonSerializerStrategy() + { + GetCache = new ReflectionUtils.ThreadSafeDictionary>(GetterValueFactory); + SetCache = new ReflectionUtils.ThreadSafeDictionary>>(SetterValueFactory); + } + + internal override IDictionary GetterValueFactory(Type type) + { + bool hasDataContract = ReflectionUtils.GetAttribute(type, typeof(DataContractAttribute)) != null; + if (!hasDataContract) + return base.GetterValueFactory(type); + string jsonKey; + IDictionary result = new Dictionary(); + foreach (PropertyInfo propertyInfo in ReflectionUtils.GetProperties(type)) + { + if (propertyInfo.CanRead) + { + MethodInfo getMethod = ReflectionUtils.GetGetterMethodInfo(propertyInfo); + if (!getMethod.IsStatic && CanAdd(propertyInfo, out jsonKey)) + result[jsonKey] = ReflectionUtils.GetGetMethod(propertyInfo); + } + } + foreach (FieldInfo fieldInfo in ReflectionUtils.GetFields(type)) + { + if (!fieldInfo.IsStatic && CanAdd(fieldInfo, out jsonKey)) + result[jsonKey] = ReflectionUtils.GetGetMethod(fieldInfo); + } + return result; + } + + internal override IDictionary> SetterValueFactory(Type type) + { + bool hasDataContract = ReflectionUtils.GetAttribute(type, typeof(DataContractAttribute)) != null; + if (!hasDataContract) + return base.SetterValueFactory(type); + string jsonKey; + IDictionary> result = new Dictionary>(); + foreach (PropertyInfo propertyInfo in ReflectionUtils.GetProperties(type)) + { + if (propertyInfo.CanWrite) + { + MethodInfo setMethod = ReflectionUtils.GetSetterMethodInfo(propertyInfo); + if (!setMethod.IsStatic && CanAdd(propertyInfo, out jsonKey)) + result[jsonKey] = new KeyValuePair(propertyInfo.PropertyType, ReflectionUtils.GetSetMethod(propertyInfo)); + } + } + foreach (FieldInfo fieldInfo in ReflectionUtils.GetFields(type)) + { + if (!fieldInfo.IsInitOnly && !fieldInfo.IsStatic && CanAdd(fieldInfo, out jsonKey)) + result[jsonKey] = new KeyValuePair(fieldInfo.FieldType, ReflectionUtils.GetSetMethod(fieldInfo)); + } + // todo implement sorting for DATACONTRACT. + return result; + } + + private static bool CanAdd(MemberInfo info, out string jsonKey) + { + jsonKey = null; + if (ReflectionUtils.GetAttribute(info, typeof(IgnoreDataMemberAttribute)) != null) + return false; + DataMemberAttribute dataMemberAttribute = (DataMemberAttribute)ReflectionUtils.GetAttribute(info, typeof(DataMemberAttribute)); + if (dataMemberAttribute == null) + return false; + jsonKey = string.IsNullOrEmpty(dataMemberAttribute.Name) ? info.Name : dataMemberAttribute.Name; + return true; + } + } + +#endif + + namespace Reflection + { + // This class is meant to be copied into other libraries. So we want to exclude it from Code Analysis rules + // that might be in place in the target project. + [GeneratedCode("reflection-utils", "1.0.0")] +#if SIMPLE_JSON_REFLECTION_UTILS_PUBLIC + public +#else + internal +#endif + class ReflectionUtils + { + private static readonly object[] EmptyObjects = new object[] { }; + + public delegate object GetDelegate(object source); + public delegate void SetDelegate(object source, object value); + public delegate object ConstructorDelegate(params object[] args); + + public delegate TValue ThreadSafeDictionaryValueFactory(TKey key); + +#if SIMPLE_JSON_TYPEINFO + public static TypeInfo GetTypeInfo(Type type) + { + return type.GetTypeInfo(); + } +#else + public static Type GetTypeInfo(Type type) + { + return type; + } +#endif + + public static Attribute GetAttribute(MemberInfo info, Type type) + { +#if SIMPLE_JSON_TYPEINFO + if (info == null || type == null || !info.IsDefined(type)) + return null; + return info.GetCustomAttribute(type); +#else + if (info == null || type == null || !Attribute.IsDefined(info, type)) + return null; + return Attribute.GetCustomAttribute(info, type); +#endif + } + + public static Type GetGenericListElementType(Type type) + { + IEnumerable interfaces; +#if SIMPLE_JSON_TYPEINFO + interfaces = type.GetTypeInfo().ImplementedInterfaces; +#else + interfaces = type.GetInterfaces(); +#endif + foreach (Type implementedInterface in interfaces) + { + if (IsTypeGeneric(implementedInterface) && + implementedInterface.GetGenericTypeDefinition() == typeof (IList<>)) + { + return GetGenericTypeArguments(implementedInterface)[0]; + } + } + return GetGenericTypeArguments(type)[0]; + } + + public static Attribute GetAttribute(Type objectType, Type attributeType) + { + +#if SIMPLE_JSON_TYPEINFO + if (objectType == null || attributeType == null || !objectType.GetTypeInfo().IsDefined(attributeType)) + return null; + return objectType.GetTypeInfo().GetCustomAttribute(attributeType); +#else + if (objectType == null || attributeType == null || !Attribute.IsDefined(objectType, attributeType)) + return null; + return Attribute.GetCustomAttribute(objectType, attributeType); +#endif + } + + public static Type[] GetGenericTypeArguments(Type type) + { +#if SIMPLE_JSON_TYPEINFO + return type.GetTypeInfo().GenericTypeArguments; +#else + return type.GetGenericArguments(); +#endif + } + + public static bool IsTypeGeneric(Type type) + { + return GetTypeInfo(type).IsGenericType; + } + + public static bool IsTypeGenericCollectionInterface(Type type) + { + if (!IsTypeGeneric(type)) + return false; + + Type genericDefinition = type.GetGenericTypeDefinition(); + + return (genericDefinition == typeof(IList<>) + || genericDefinition == typeof(ICollection<>) + || genericDefinition == typeof(IEnumerable<>) +#if SIMPLE_JSON_READONLY_COLLECTIONS + || genericDefinition == typeof(IReadOnlyCollection<>) + || genericDefinition == typeof(IReadOnlyList<>) +#endif + ); + } + + public static bool IsAssignableFrom(Type type1, Type type2) + { + return GetTypeInfo(type1).IsAssignableFrom(GetTypeInfo(type2)); + } + + public static bool IsTypeDictionary(Type type) + { +#if SIMPLE_JSON_TYPEINFO + if (typeof(IDictionary<,>).GetTypeInfo().IsAssignableFrom(type.GetTypeInfo())) + return true; +#else + if (typeof(System.Collections.IDictionary).IsAssignableFrom(type)) + return true; +#endif + if (!GetTypeInfo(type).IsGenericType) + return false; + + Type genericDefinition = type.GetGenericTypeDefinition(); + return genericDefinition == typeof(IDictionary<,>); + } + + public static bool IsNullableType(Type type) + { + return GetTypeInfo(type).IsGenericType && type.GetGenericTypeDefinition() == typeof(Nullable<>); + } + + public static object ToNullableType(object obj, Type nullableType) + { + return obj == null ? null : Convert.ChangeType(obj, Nullable.GetUnderlyingType(nullableType), CultureInfo.InvariantCulture); + } + + public static bool IsValueType(Type type) + { + return GetTypeInfo(type).IsValueType; + } + + public static IEnumerable GetConstructors(Type type) + { +#if SIMPLE_JSON_TYPEINFO + return type.GetTypeInfo().DeclaredConstructors; +#else + const BindingFlags flags = BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic; + return type.GetConstructors(flags); +#endif + } + + public static ConstructorInfo GetConstructorInfo(Type type, params Type[] argsType) + { + IEnumerable constructorInfos = GetConstructors(type); + int i; + bool matches; + foreach (ConstructorInfo constructorInfo in constructorInfos) + { + ParameterInfo[] parameters = constructorInfo.GetParameters(); + if (argsType.Length != parameters.Length) + continue; + + i = 0; + matches = true; + foreach (ParameterInfo parameterInfo in constructorInfo.GetParameters()) + { + if (parameterInfo.ParameterType != argsType[i]) + { + matches = false; + break; + } + } + + if (matches) + return constructorInfo; + } + + return null; + } + + public static IEnumerable GetProperties(Type type) + { +#if SIMPLE_JSON_TYPEINFO + return type.GetRuntimeProperties(); +#else + return type.GetProperties(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Static); +#endif + } + + public static IEnumerable GetFields(Type type) + { +#if SIMPLE_JSON_TYPEINFO + return type.GetRuntimeFields(); +#else + return type.GetFields(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Static); +#endif + } + + public static MethodInfo GetGetterMethodInfo(PropertyInfo propertyInfo) + { +#if SIMPLE_JSON_TYPEINFO + return propertyInfo.GetMethod; +#else + return propertyInfo.GetGetMethod(true); +#endif + } + + public static MethodInfo GetSetterMethodInfo(PropertyInfo propertyInfo) + { +#if SIMPLE_JSON_TYPEINFO + return propertyInfo.SetMethod; +#else + return propertyInfo.GetSetMethod(true); +#endif + } + + public static ConstructorDelegate GetConstructor(ConstructorInfo constructorInfo) + { +#if SIMPLE_JSON_NO_LINQ_EXPRESSION + return GetConstructorByReflection(constructorInfo); +#else + return GetConstructorByExpression(constructorInfo); +#endif + } + + public static ConstructorDelegate GetConstructor(Type type, params Type[] argsType) + { +#if SIMPLE_JSON_NO_LINQ_EXPRESSION + return GetConstructorByReflection(type, argsType); +#else + return GetConstructorByExpression(type, argsType); +#endif + } + + public static ConstructorDelegate GetConstructorByReflection(ConstructorInfo constructorInfo) + { + return delegate(object[] args) { return constructorInfo.Invoke(args); }; + } + + public static ConstructorDelegate GetConstructorByReflection(Type type, params Type[] argsType) + { + ConstructorInfo constructorInfo = GetConstructorInfo(type, argsType); + + if (constructorInfo == null && argsType.Length == 0 && type.IsValueType) + { + // If it's a struct, then parameterless constructors are implicit + // We can always call Activator.CreateInstance in lieu of a zero-arg constructor + return args => Activator.CreateInstance(type); + } + + return constructorInfo == null ? null : GetConstructorByReflection(constructorInfo); + } + +#if !SIMPLE_JSON_NO_LINQ_EXPRESSION + + public static ConstructorDelegate GetConstructorByExpression(ConstructorInfo constructorInfo) + { + ParameterInfo[] paramsInfo = constructorInfo.GetParameters(); + ParameterExpression param = Expression.Parameter(typeof(object[]), "args"); + Expression[] argsExp = new Expression[paramsInfo.Length]; + for (int i = 0; i < paramsInfo.Length; i++) + { + Expression index = Expression.Constant(i); + Type paramType = paramsInfo[i].ParameterType; + Expression paramAccessorExp = Expression.ArrayIndex(param, index); + Expression paramCastExp = Expression.Convert(paramAccessorExp, paramType); + argsExp[i] = paramCastExp; + } + NewExpression newExp = Expression.New(constructorInfo, argsExp); + Expression> lambda = Expression.Lambda>(newExp, param); + Func compiledLambda = lambda.Compile(); + return delegate(object[] args) { return compiledLambda(args); }; + } + + public static ConstructorDelegate GetConstructorByExpression(Type type, params Type[] argsType) + { + ConstructorInfo constructorInfo = GetConstructorInfo(type, argsType); + return constructorInfo == null ? null : GetConstructorByExpression(constructorInfo); + } + +#endif + + public static GetDelegate GetGetMethod(PropertyInfo propertyInfo) + { +#if SIMPLE_JSON_NO_LINQ_EXPRESSION + return GetGetMethodByReflection(propertyInfo); +#else + return GetGetMethodByExpression(propertyInfo); +#endif + } + + public static GetDelegate GetGetMethod(FieldInfo fieldInfo) + { +#if SIMPLE_JSON_NO_LINQ_EXPRESSION + return GetGetMethodByReflection(fieldInfo); +#else + return GetGetMethodByExpression(fieldInfo); +#endif + } + + public static GetDelegate GetGetMethodByReflection(PropertyInfo propertyInfo) + { + MethodInfo methodInfo = GetGetterMethodInfo(propertyInfo); + return delegate(object source) { return methodInfo.Invoke(source, EmptyObjects); }; + } + + public static GetDelegate GetGetMethodByReflection(FieldInfo fieldInfo) + { + return delegate(object source) { return fieldInfo.GetValue(source); }; + } + +#if !SIMPLE_JSON_NO_LINQ_EXPRESSION + + public static GetDelegate GetGetMethodByExpression(PropertyInfo propertyInfo) + { + MethodInfo getMethodInfo = GetGetterMethodInfo(propertyInfo); + ParameterExpression instance = Expression.Parameter(typeof(object), "instance"); + UnaryExpression instanceCast = (!IsValueType(propertyInfo.DeclaringType)) ? Expression.TypeAs(instance, propertyInfo.DeclaringType) : Expression.Convert(instance, propertyInfo.DeclaringType); + Func compiled = Expression.Lambda>(Expression.TypeAs(Expression.Call(instanceCast, getMethodInfo), typeof(object)), instance).Compile(); + return delegate(object source) { return compiled(source); }; + } + + public static GetDelegate GetGetMethodByExpression(FieldInfo fieldInfo) + { + ParameterExpression instance = Expression.Parameter(typeof(object), "instance"); + MemberExpression member = Expression.Field(Expression.Convert(instance, fieldInfo.DeclaringType), fieldInfo); + GetDelegate compiled = Expression.Lambda(Expression.Convert(member, typeof(object)), instance).Compile(); + return delegate(object source) { return compiled(source); }; + } + +#endif + + public static SetDelegate GetSetMethod(PropertyInfo propertyInfo) + { +#if SIMPLE_JSON_NO_LINQ_EXPRESSION + return GetSetMethodByReflection(propertyInfo); +#else + return GetSetMethodByExpression(propertyInfo); +#endif + } + + public static SetDelegate GetSetMethod(FieldInfo fieldInfo) + { +#if SIMPLE_JSON_NO_LINQ_EXPRESSION + return GetSetMethodByReflection(fieldInfo); +#else + return GetSetMethodByExpression(fieldInfo); +#endif + } + + public static SetDelegate GetSetMethodByReflection(PropertyInfo propertyInfo) + { + MethodInfo methodInfo = GetSetterMethodInfo(propertyInfo); + return delegate(object source, object value) { methodInfo.Invoke(source, new object[] { value }); }; + } + + public static SetDelegate GetSetMethodByReflection(FieldInfo fieldInfo) + { + return delegate(object source, object value) { fieldInfo.SetValue(source, value); }; + } + +#if !SIMPLE_JSON_NO_LINQ_EXPRESSION + + public static SetDelegate GetSetMethodByExpression(PropertyInfo propertyInfo) + { + MethodInfo setMethodInfo = GetSetterMethodInfo(propertyInfo); + ParameterExpression instance = Expression.Parameter(typeof(object), "instance"); + ParameterExpression value = Expression.Parameter(typeof(object), "value"); + UnaryExpression instanceCast = (!IsValueType(propertyInfo.DeclaringType)) ? Expression.TypeAs(instance, propertyInfo.DeclaringType) : Expression.Convert(instance, propertyInfo.DeclaringType); + UnaryExpression valueCast = (!IsValueType(propertyInfo.PropertyType)) ? Expression.TypeAs(value, propertyInfo.PropertyType) : Expression.Convert(value, propertyInfo.PropertyType); + Action compiled = Expression.Lambda>(Expression.Call(instanceCast, setMethodInfo, valueCast), new ParameterExpression[] { instance, value }).Compile(); + return delegate(object source, object val) { compiled(source, val); }; + } + + public static SetDelegate GetSetMethodByExpression(FieldInfo fieldInfo) + { + ParameterExpression instance = Expression.Parameter(typeof(object), "instance"); + ParameterExpression value = Expression.Parameter(typeof(object), "value"); + Action compiled = Expression.Lambda>( + Assign(Expression.Field(Expression.Convert(instance, fieldInfo.DeclaringType), fieldInfo), Expression.Convert(value, fieldInfo.FieldType)), instance, value).Compile(); + return delegate(object source, object val) { compiled(source, val); }; + } + + public static BinaryExpression Assign(Expression left, Expression right) + { +#if SIMPLE_JSON_TYPEINFO + return Expression.Assign(left, right); +#else + MethodInfo assign = typeof(Assigner<>).MakeGenericType(left.Type).GetMethod("Assign"); + BinaryExpression assignExpr = Expression.Add(left, right, assign); + return assignExpr; +#endif + } + + private static class Assigner + { + public static T Assign(ref T left, T right) + { + return (left = right); + } + } + +#endif + + public sealed class ThreadSafeDictionary : IDictionary + { + private readonly object _lock = new object(); + private readonly ThreadSafeDictionaryValueFactory _valueFactory; + private Dictionary _dictionary; + + public ThreadSafeDictionary(ThreadSafeDictionaryValueFactory valueFactory) + { + _valueFactory = valueFactory; + } + + private TValue Get(TKey key) + { + if (_dictionary == null) + return AddValue(key); + TValue value; + if (!_dictionary.TryGetValue(key, out value)) + return AddValue(key); + return value; + } + + private TValue AddValue(TKey key) + { + TValue value = _valueFactory(key); + lock (_lock) + { + if (_dictionary == null) + { + _dictionary = new Dictionary(); + _dictionary[key] = value; + } + else + { + TValue val; + if (_dictionary.TryGetValue(key, out val)) + return val; + Dictionary dict = new Dictionary(_dictionary); + dict[key] = value; + _dictionary = dict; + } + } + return value; + } + + public void Add(TKey key, TValue value) + { + throw new NotImplementedException(); + } + + public bool ContainsKey(TKey key) + { + return _dictionary.ContainsKey(key); + } + + public ICollection Keys + { + get { return _dictionary.Keys; } + } + + public bool Remove(TKey key) + { + throw new NotImplementedException(); + } + + public bool TryGetValue(TKey key, out TValue value) + { + value = this[key]; + return true; + } + + public ICollection Values + { + get { return _dictionary.Values; } + } + + public TValue this[TKey key] + { + get { return Get(key); } + set { throw new NotImplementedException(); } + } + + public void Add(KeyValuePair item) + { + throw new NotImplementedException(); + } + + public void Clear() + { + throw new NotImplementedException(); + } + + public bool Contains(KeyValuePair item) + { + throw new NotImplementedException(); + } + + public void CopyTo(KeyValuePair[] array, int arrayIndex) + { + throw new NotImplementedException(); + } + + public int Count + { + get { return _dictionary.Count; } + } + + public bool IsReadOnly + { + get { throw new NotImplementedException(); } + } + + public bool Remove(KeyValuePair item) + { + throw new NotImplementedException(); + } + + public IEnumerator> GetEnumerator() + { + return _dictionary.GetEnumerator(); + } + + System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() + { + return _dictionary.GetEnumerator(); + } + } + + } + } +} +// ReSharper restore LoopCanBeConvertedToQuery +// ReSharper restore RedundantExplicitArrayCreation +// ReSharper restore SuggestUseVarKeywordEvident diff --git a/src/JSInterop/src/Microsoft.JSInterop/Microsoft.JSInterop.csproj b/src/JSInterop/src/Microsoft.JSInterop/Microsoft.JSInterop.csproj new file mode 100644 index 0000000000..9f5c4f4abb --- /dev/null +++ b/src/JSInterop/src/Microsoft.JSInterop/Microsoft.JSInterop.csproj @@ -0,0 +1,7 @@ + + + + netstandard2.0 + + + diff --git a/src/JSInterop/src/Microsoft.JSInterop/Properties/AssemblyInfo.cs b/src/JSInterop/src/Microsoft.JSInterop/Properties/AssemblyInfo.cs new file mode 100644 index 0000000000..d65c89dc7f --- /dev/null +++ b/src/JSInterop/src/Microsoft.JSInterop/Properties/AssemblyInfo.cs @@ -0,0 +1,3 @@ +using System.Runtime.CompilerServices; + +[assembly: InternalsVisibleTo("Microsoft.JSInterop.Test, PublicKey=0024000004800000940000000602000000240000525341310004000001000100f33a29044fa9d740c9b3213a93e57c84b472c84e0b8a0e1ae48e67a9f8f6de9d5f7f3d52ac23e48ac51801f1dc950abe901da34d2a9e3baadb141a17c77ef3c565dd5ee5054b91cf63bb3c6ab83f72ab3aafe93d0fc3c2348b764fafb0b1c0733de51459aeab46580384bf9d74c4e28164b7cde247f891ba07891c9d872ad2bb")] diff --git a/src/JSInterop/src/Microsoft.JSInterop/TaskGenericsUtil.cs b/src/JSInterop/src/Microsoft.JSInterop/TaskGenericsUtil.cs new file mode 100644 index 0000000000..734e9863b8 --- /dev/null +++ b/src/JSInterop/src/Microsoft.JSInterop/TaskGenericsUtil.cs @@ -0,0 +1,116 @@ +// 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.Collections.Concurrent; +using System.Linq; +using System.Threading.Tasks; + +namespace Microsoft.JSInterop +{ + internal static class TaskGenericsUtil + { + private static ConcurrentDictionary _cachedResultGetters + = new ConcurrentDictionary(); + + private static ConcurrentDictionary _cachedResultSetters + = new ConcurrentDictionary(); + + public static void SetTaskCompletionSourceResult(object taskCompletionSource, object result) + => CreateResultSetter(taskCompletionSource).SetResult(taskCompletionSource, result); + + public static void SetTaskCompletionSourceException(object taskCompletionSource, Exception exception) + => CreateResultSetter(taskCompletionSource).SetException(taskCompletionSource, exception); + + public static Type GetTaskCompletionSourceResultType(object taskCompletionSource) + => CreateResultSetter(taskCompletionSource).ResultType; + + public static object GetTaskResult(Task task) + { + var getter = _cachedResultGetters.GetOrAdd(task.GetType(), taskInstanceType => + { + var resultType = GetTaskResultType(taskInstanceType); + return resultType == null + ? new VoidTaskResultGetter() + : (ITaskResultGetter)Activator.CreateInstance( + typeof(TaskResultGetter<>).MakeGenericType(resultType)); + }); + return getter.GetResult(task); + } + + private static Type GetTaskResultType(Type taskType) + { + // It might be something derived from Task or Task, so we have to scan + // up the inheritance hierarchy to find the Task or Task + while (taskType != typeof(Task) && + (!taskType.IsGenericType || taskType.GetGenericTypeDefinition() != typeof(Task<>))) + { + taskType = taskType.BaseType + ?? throw new ArgumentException($"The type '{taskType.FullName}' is not inherited from '{typeof(Task).FullName}'."); + } + + return taskType.IsGenericType + ? taskType.GetGenericArguments().Single() + : null; + } + + interface ITcsResultSetter + { + Type ResultType { get; } + void SetResult(object taskCompletionSource, object result); + void SetException(object taskCompletionSource, Exception exception); + } + + private interface ITaskResultGetter + { + object GetResult(Task task); + } + + private class TaskResultGetter : ITaskResultGetter + { + public object GetResult(Task task) => ((Task)task).Result; + } + + private class VoidTaskResultGetter : ITaskResultGetter + { + public object GetResult(Task task) + { + task.Wait(); // Throw if the task failed + return null; + } + } + + private class TcsResultSetter : ITcsResultSetter + { + public Type ResultType => typeof(T); + + public void SetResult(object tcs, object result) + { + var typedTcs = (TaskCompletionSource)tcs; + + // If necessary, attempt a cast + var typedResult = result is T resultT + ? resultT + : (T)Convert.ChangeType(result, typeof(T)); + + typedTcs.SetResult(typedResult); + } + + public void SetException(object tcs, Exception exception) + { + var typedTcs = (TaskCompletionSource)tcs; + typedTcs.SetException(exception); + } + } + + private static ITcsResultSetter CreateResultSetter(object taskCompletionSource) + { + return _cachedResultSetters.GetOrAdd(taskCompletionSource.GetType(), tcsType => + { + var resultType = tcsType.GetGenericArguments().Single(); + return (ITcsResultSetter)Activator.CreateInstance( + typeof(TcsResultSetter<>).MakeGenericType(resultType)); + }); + } + } +} diff --git a/src/JSInterop/src/Mono.WebAssembly.Interop/InternalCalls.cs b/src/JSInterop/src/Mono.WebAssembly.Interop/InternalCalls.cs new file mode 100644 index 0000000000..60c0cdc429 --- /dev/null +++ b/src/JSInterop/src/Mono.WebAssembly.Interop/InternalCalls.cs @@ -0,0 +1,25 @@ +// 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.Runtime.CompilerServices; + +namespace WebAssembly.JSInterop +{ + /// + /// Methods that map to the functions compiled into the Mono WebAssembly runtime, + /// as defined by 'mono_add_internal_call' calls in driver.c + /// + internal class InternalCalls + { + // The exact namespace, type, and method names must match the corresponding entries + // in driver.c in the Mono distribution + + // We're passing asyncHandle by ref not because we want it to be writable, but so it gets + // passed as a pointer (4 bytes). We can pass 4-byte values, but not 8-byte ones. + [MethodImpl(MethodImplOptions.InternalCall)] + public static extern string InvokeJSMarshalled(out string exception, ref long asyncHandle, string functionIdentifier, string argsJson); + + [MethodImpl(MethodImplOptions.InternalCall)] + public static extern TRes InvokeJSUnmarshalled(out string exception, string functionIdentifier, T0 arg0, T1 arg1, T2 arg2); + } +} diff --git a/src/JSInterop/src/Mono.WebAssembly.Interop/Mono.WebAssembly.Interop.csproj b/src/JSInterop/src/Mono.WebAssembly.Interop/Mono.WebAssembly.Interop.csproj new file mode 100644 index 0000000000..81f1173b55 --- /dev/null +++ b/src/JSInterop/src/Mono.WebAssembly.Interop/Mono.WebAssembly.Interop.csproj @@ -0,0 +1,11 @@ + + + + netstandard2.0 + + + + + + + diff --git a/src/JSInterop/src/Mono.WebAssembly.Interop/MonoWebAssemblyJSRuntime.cs b/src/JSInterop/src/Mono.WebAssembly.Interop/MonoWebAssemblyJSRuntime.cs new file mode 100644 index 0000000000..9a502b8bc8 --- /dev/null +++ b/src/JSInterop/src/Mono.WebAssembly.Interop/MonoWebAssemblyJSRuntime.cs @@ -0,0 +1,114 @@ +// 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 Microsoft.JSInterop; +using WebAssembly.JSInterop; + +namespace Mono.WebAssembly.Interop +{ + /// + /// Provides methods for invoking JavaScript functions for applications running + /// on the Mono WebAssembly runtime. + /// + public class MonoWebAssemblyJSRuntime : JSInProcessRuntimeBase + { + /// + protected override string InvokeJS(string identifier, string argsJson) + { + var noAsyncHandle = default(long); + var result = InternalCalls.InvokeJSMarshalled(out var exception, ref noAsyncHandle, identifier, argsJson); + return exception != null + ? throw new JSException(exception) + : result; + } + + /// + protected override void BeginInvokeJS(long asyncHandle, string identifier, string argsJson) + { + InternalCalls.InvokeJSMarshalled(out _, ref asyncHandle, identifier, argsJson); + } + + // Invoked via Mono's JS interop mechanism (invoke_method) + private static string InvokeDotNet(string assemblyName, string methodIdentifier, string dotNetObjectId, string argsJson) + => DotNetDispatcher.Invoke(assemblyName, methodIdentifier, dotNetObjectId == null ? default : long.Parse(dotNetObjectId), argsJson); + + // Invoked via Mono's JS interop mechanism (invoke_method) + private static void BeginInvokeDotNet(string callId, string assemblyNameOrDotNetObjectId, string methodIdentifier, string argsJson) + { + // Figure out whether 'assemblyNameOrDotNetObjectId' is the assembly name or the instance ID + // We only need one for any given call. This helps to work around the limitation that we can + // only pass a maximum of 4 args in a call from JS to Mono WebAssembly. + string assemblyName; + long dotNetObjectId; + if (char.IsDigit(assemblyNameOrDotNetObjectId[0])) + { + dotNetObjectId = long.Parse(assemblyNameOrDotNetObjectId); + assemblyName = null; + } + else + { + dotNetObjectId = default; + assemblyName = assemblyNameOrDotNetObjectId; + } + + DotNetDispatcher.BeginInvoke(callId, assemblyName, methodIdentifier, dotNetObjectId, argsJson); + } + + #region Custom MonoWebAssemblyJSRuntime methods + + /// + /// Invokes the JavaScript function registered with the specified identifier. + /// + /// The .NET type corresponding to the function's return value type. + /// The identifier used when registering the target function. + /// The result of the function invocation. + public TRes InvokeUnmarshalled(string identifier) + => InvokeUnmarshalled(identifier, null, null, null); + + /// + /// Invokes the JavaScript function registered with the specified identifier. + /// + /// The type of the first argument. + /// The .NET type corresponding to the function's return value type. + /// The identifier used when registering the target function. + /// The first argument. + /// The result of the function invocation. + public TRes InvokeUnmarshalled(string identifier, T0 arg0) + => InvokeUnmarshalled(identifier, arg0, null, null); + + /// + /// Invokes the JavaScript function registered with the specified identifier. + /// + /// The type of the first argument. + /// The type of the second argument. + /// The .NET type corresponding to the function's return value type. + /// The identifier used when registering the target function. + /// The first argument. + /// The second argument. + /// The result of the function invocation. + public TRes InvokeUnmarshalled(string identifier, T0 arg0, T1 arg1) + => InvokeUnmarshalled(identifier, arg0, arg1, null); + + /// + /// Invokes the JavaScript function registered with the specified identifier. + /// + /// The type of the first argument. + /// The type of the second argument. + /// The type of the third argument. + /// The .NET type corresponding to the function's return value type. + /// The identifier used when registering the target function. + /// The first argument. + /// The second argument. + /// The third argument. + /// The result of the function invocation. + public TRes InvokeUnmarshalled(string identifier, T0 arg0, T1 arg1, T2 arg2) + { + var result = InternalCalls.InvokeJSUnmarshalled(out var exception, identifier, arg0, arg1, arg2); + return exception != null + ? throw new JSException(exception) + : result; + } + + #endregion + } +} diff --git a/src/JSInterop/test/Microsoft.JSInterop.Test/DotNetDispatcherTest.cs b/src/JSInterop/test/Microsoft.JSInterop.Test/DotNetDispatcherTest.cs new file mode 100644 index 0000000000..eec537f987 --- /dev/null +++ b/src/JSInterop/test/Microsoft.JSInterop.Test/DotNetDispatcherTest.cs @@ -0,0 +1,443 @@ +// 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.Linq; +using System.Threading.Tasks; +using Xunit; + +namespace Microsoft.JSInterop.Test +{ + public class DotNetDispatcherTest + { + private readonly static string thisAssemblyName + = typeof(DotNetDispatcherTest).Assembly.GetName().Name; + private readonly TestJSRuntime jsRuntime + = new TestJSRuntime(); + + [Fact] + public void CannotInvokeWithEmptyAssemblyName() + { + var ex = Assert.Throws(() => + { + DotNetDispatcher.Invoke(" ", "SomeMethod", default, "[]"); + }); + + Assert.StartsWith("Cannot be null, empty, or whitespace.", ex.Message); + Assert.Equal("assemblyName", ex.ParamName); + } + + [Fact] + public void CannotInvokeWithEmptyMethodIdentifier() + { + var ex = Assert.Throws(() => + { + DotNetDispatcher.Invoke("SomeAssembly", " ", default, "[]"); + }); + + Assert.StartsWith("Cannot be null, empty, or whitespace.", ex.Message); + Assert.Equal("methodIdentifier", ex.ParamName); + } + + [Fact] + public void CannotInvokeMethodsOnUnloadedAssembly() + { + var assemblyName = "Some.Fake.Assembly"; + var ex = Assert.Throws(() => + { + DotNetDispatcher.Invoke(assemblyName, "SomeMethod", default, null); + }); + + Assert.Equal($"There is no loaded assembly with the name '{assemblyName}'.", ex.Message); + } + + // Note: Currently it's also not possible to invoke generic methods. + // That's not something determined by DotNetDispatcher, but rather by the fact that we + // don't close over the generics in the reflection code. + // Not defining this behavior through unit tests because the default outcome is + // fine (an exception stating what info is missing). + + [Theory] + [InlineData("MethodOnInternalType")] + [InlineData("PrivateMethod")] + [InlineData("ProtectedMethod")] + [InlineData("StaticMethodWithoutAttribute")] // That's not really its identifier; just making the point that there's no way to invoke it + [InlineData("InstanceMethodWithoutAttribute")] // That's not really its identifier; just making the point that there's no way to invoke it + public void CannotInvokeUnsuitableMethods(string methodIdentifier) + { + var ex = Assert.Throws(() => + { + DotNetDispatcher.Invoke(thisAssemblyName, methodIdentifier, default, null); + }); + + Assert.Equal($"The assembly '{thisAssemblyName}' does not contain a public method with [JSInvokableAttribute(\"{methodIdentifier}\")].", ex.Message); + } + + [Fact] + public Task CanInvokeStaticVoidMethod() => WithJSRuntime(jsRuntime => + { + // Arrange/Act + SomePublicType.DidInvokeMyInvocableStaticVoid = false; + var resultJson = DotNetDispatcher.Invoke(thisAssemblyName, "InvocableStaticVoid", default, null); + + // Assert + Assert.Null(resultJson); + Assert.True(SomePublicType.DidInvokeMyInvocableStaticVoid); + }); + + [Fact] + public Task CanInvokeStaticNonVoidMethod() => WithJSRuntime(jsRuntime => + { + // Arrange/Act + var resultJson = DotNetDispatcher.Invoke(thisAssemblyName, "InvocableStaticNonVoid", default, null); + var result = Json.Deserialize(resultJson); + + // Assert + Assert.Equal("Test", result.StringVal); + Assert.Equal(123, result.IntVal); + }); + + [Fact] + public Task CanInvokeStaticNonVoidMethodWithoutCustomIdentifier() => WithJSRuntime(jsRuntime => + { + // Arrange/Act + var resultJson = DotNetDispatcher.Invoke(thisAssemblyName, nameof(SomePublicType.InvokableMethodWithoutCustomIdentifier), default, null); + var result = Json.Deserialize(resultJson); + + // Assert + Assert.Equal("InvokableMethodWithoutCustomIdentifier", result.StringVal); + Assert.Equal(456, result.IntVal); + }); + + [Fact] + public Task CanInvokeStaticWithParams() => WithJSRuntime(jsRuntime => + { + // Arrange: Track a .NET object to use as an arg + var arg3 = new TestDTO { IntVal = 999, StringVal = "My string" }; + jsRuntime.Invoke("unimportant", new DotNetObjectRef(arg3)); + + // Arrange: Remaining args + var argsJson = Json.Serialize(new object[] { + new TestDTO { StringVal = "Another string", IntVal = 456 }, + new[] { 100, 200 }, + "__dotNetObject:1" + }); + + // Act + var resultJson = DotNetDispatcher.Invoke(thisAssemblyName, "InvocableStaticWithParams", default, argsJson); + var result = Json.Deserialize(resultJson); + + // Assert: First result value marshalled via JSON + var resultDto1 = (TestDTO)jsRuntime.ArgSerializerStrategy.DeserializeObject(result[0], typeof(TestDTO)); + Assert.Equal("ANOTHER STRING", resultDto1.StringVal); + Assert.Equal(756, resultDto1.IntVal); + + // Assert: Second result value marshalled by ref + var resultDto2Ref = (string)result[1]; + Assert.Equal("__dotNetObject:2", resultDto2Ref); + var resultDto2 = (TestDTO)jsRuntime.ArgSerializerStrategy.FindDotNetObject(2); + Assert.Equal("MY STRING", resultDto2.StringVal); + Assert.Equal(1299, resultDto2.IntVal); + }); + + [Fact] + public Task CanInvokeInstanceVoidMethod() => WithJSRuntime(jsRuntime => + { + // Arrange: Track some instance + var targetInstance = new SomePublicType(); + jsRuntime.Invoke("unimportant", new DotNetObjectRef(targetInstance)); + + // Act + var resultJson = DotNetDispatcher.Invoke(null, "InvokableInstanceVoid", 1, null); + + // Assert + Assert.Null(resultJson); + Assert.True(targetInstance.DidInvokeMyInvocableInstanceVoid); + }); + + [Fact] + public Task CanInvokeBaseInstanceVoidMethod() => WithJSRuntime(jsRuntime => + { + // Arrange: Track some instance + var targetInstance = new DerivedClass(); + jsRuntime.Invoke("unimportant", new DotNetObjectRef(targetInstance)); + + // Act + var resultJson = DotNetDispatcher.Invoke(null, "BaseClassInvokableInstanceVoid", 1, null); + + // Assert + Assert.Null(resultJson); + Assert.True(targetInstance.DidInvokeMyBaseClassInvocableInstanceVoid); + }); + + [Fact] + public Task CannotUseDotNetObjectRefAfterDisposal() => WithJSRuntime(jsRuntime => + { + // This test addresses the case where the developer calls objectRef.Dispose() + // from .NET code, as opposed to .dispose() from JS code + + // Arrange: Track some instance, then dispose it + var targetInstance = new SomePublicType(); + var objectRef = new DotNetObjectRef(targetInstance); + jsRuntime.Invoke("unimportant", objectRef); + objectRef.Dispose(); + + // Act/Assert + var ex = Assert.Throws( + () => DotNetDispatcher.Invoke(null, "InvokableInstanceVoid", 1, null)); + Assert.StartsWith("There is no tracked object with id '1'.", ex.Message); + }); + + [Fact] + public Task CannotUseDotNetObjectRefAfterReleaseDotNetObject() => WithJSRuntime(jsRuntime => + { + // This test addresses the case where the developer calls .dispose() + // from JS code, as opposed to objectRef.Dispose() from .NET code + + // Arrange: Track some instance, then dispose it + var targetInstance = new SomePublicType(); + var objectRef = new DotNetObjectRef(targetInstance); + jsRuntime.Invoke("unimportant", objectRef); + DotNetDispatcher.ReleaseDotNetObject(1); + + // Act/Assert + var ex = Assert.Throws( + () => DotNetDispatcher.Invoke(null, "InvokableInstanceVoid", 1, null)); + Assert.StartsWith("There is no tracked object with id '1'.", ex.Message); + }); + + [Fact] + public Task CanInvokeInstanceMethodWithParams() => WithJSRuntime(jsRuntime => + { + // Arrange: Track some instance plus another object we'll pass as a param + var targetInstance = new SomePublicType(); + var arg2 = new TestDTO { IntVal = 1234, StringVal = "My string" }; + jsRuntime.Invoke("unimportant", + new DotNetObjectRef(targetInstance), + new DotNetObjectRef(arg2)); + var argsJson = "[\"myvalue\",\"__dotNetObject:2\"]"; + + // Act + var resultJson = DotNetDispatcher.Invoke(null, "InvokableInstanceMethod", 1, argsJson); + + // Assert + Assert.Equal("[\"You passed myvalue\",\"__dotNetObject:3\"]", resultJson); + var resultDto = (TestDTO)jsRuntime.ArgSerializerStrategy.FindDotNetObject(3); + Assert.Equal(1235, resultDto.IntVal); + Assert.Equal("MY STRING", resultDto.StringVal); + }); + + [Fact] + public void CannotInvokeWithIncorrectNumberOfParams() + { + // Arrange + var argsJson = Json.Serialize(new object[] { 1, 2, 3, 4 }); + + // Act/Assert + var ex = Assert.Throws(() => + { + DotNetDispatcher.Invoke(thisAssemblyName, "InvocableStaticWithParams", default, argsJson); + }); + + Assert.Equal("In call to 'InvocableStaticWithParams', expected 3 parameters but received 4.", ex.Message); + } + + [Fact] + public Task CanInvokeAsyncMethod() => WithJSRuntime(async jsRuntime => + { + // Arrange: Track some instance plus another object we'll pass as a param + var targetInstance = new SomePublicType(); + var arg2 = new TestDTO { IntVal = 1234, StringVal = "My string" }; + jsRuntime.Invoke("unimportant", new DotNetObjectRef(targetInstance), new DotNetObjectRef(arg2)); + + // Arrange: all args + var argsJson = Json.Serialize(new object[] + { + new TestDTO { IntVal = 1000, StringVal = "String via JSON" }, + "__dotNetObject:2" + }); + + // Act + var callId = "123"; + var resultTask = jsRuntime.NextInvocationTask; + DotNetDispatcher.BeginInvoke(callId, null, "InvokableAsyncMethod", 1, argsJson); + await resultTask; + var result = Json.Deserialize(jsRuntime.LastInvocationArgsJson); + var resultValue = (SimpleJson.JsonArray)result[2]; + + // Assert: Correct info to complete the async call + Assert.Equal(0, jsRuntime.LastInvocationAsyncHandle); // 0 because it doesn't want a further callback from JS to .NET + Assert.Equal("DotNet.jsCallDispatcher.endInvokeDotNetFromJS", jsRuntime.LastInvocationIdentifier); + Assert.Equal(3, result.Count); + Assert.Equal(callId, result[0]); + Assert.True((bool)result[1]); // Success flag + + // Assert: First result value marshalled via JSON + var resultDto1 = (TestDTO)jsRuntime.ArgSerializerStrategy.DeserializeObject(resultValue[0], typeof(TestDTO)); + Assert.Equal("STRING VIA JSON", resultDto1.StringVal); + Assert.Equal(2000, resultDto1.IntVal); + + // Assert: Second result value marshalled by ref + var resultDto2Ref = (string)resultValue[1]; + Assert.Equal("__dotNetObject:3", resultDto2Ref); + var resultDto2 = (TestDTO)jsRuntime.ArgSerializerStrategy.FindDotNetObject(3); + Assert.Equal("MY STRING", resultDto2.StringVal); + Assert.Equal(2468, resultDto2.IntVal); + }); + + Task WithJSRuntime(Action testCode) + { + return WithJSRuntime(jsRuntime => + { + testCode(jsRuntime); + return Task.CompletedTask; + }); + } + + async Task WithJSRuntime(Func testCode) + { + // Since the tests rely on the asynclocal JSRuntime.Current, ensure we + // are on a distinct async context with a non-null JSRuntime.Current + await Task.Yield(); + + var runtime = new TestJSRuntime(); + JSRuntime.SetCurrentJSRuntime(runtime); + await testCode(runtime); + } + + internal class SomeInteralType + { + [JSInvokable("MethodOnInternalType")] public void MyMethod() { } + } + + public class SomePublicType + { + public static bool DidInvokeMyInvocableStaticVoid; + public bool DidInvokeMyInvocableInstanceVoid; + + [JSInvokable("PrivateMethod")] private static void MyPrivateMethod() { } + [JSInvokable("ProtectedMethod")] protected static void MyProtectedMethod() { } + protected static void StaticMethodWithoutAttribute() { } + protected static void InstanceMethodWithoutAttribute() { } + + [JSInvokable("InvocableStaticVoid")] public static void MyInvocableVoid() + { + DidInvokeMyInvocableStaticVoid = true; + } + + [JSInvokable("InvocableStaticNonVoid")] + public static object MyInvocableNonVoid() + => new TestDTO { StringVal = "Test", IntVal = 123 }; + + [JSInvokable("InvocableStaticWithParams")] + public static object[] MyInvocableWithParams(TestDTO dtoViaJson, int[] incrementAmounts, TestDTO dtoByRef) + => new object[] + { + new TestDTO // Return via JSON marshalling + { + StringVal = dtoViaJson.StringVal.ToUpperInvariant(), + IntVal = dtoViaJson.IntVal + incrementAmounts.Sum() + }, + new DotNetObjectRef(new TestDTO // Return by ref + { + StringVal = dtoByRef.StringVal.ToUpperInvariant(), + IntVal = dtoByRef.IntVal + incrementAmounts.Sum() + }) + }; + + [JSInvokable] + public static TestDTO InvokableMethodWithoutCustomIdentifier() + => new TestDTO { StringVal = "InvokableMethodWithoutCustomIdentifier", IntVal = 456 }; + + [JSInvokable] + public void InvokableInstanceVoid() + { + DidInvokeMyInvocableInstanceVoid = true; + } + + [JSInvokable] + public object[] InvokableInstanceMethod(string someString, TestDTO someDTO) + { + // Returning an array to make the point that object references + // can be embedded anywhere in the result + return new object[] + { + $"You passed {someString}", + new DotNetObjectRef(new TestDTO + { + IntVal = someDTO.IntVal + 1, + StringVal = someDTO.StringVal.ToUpperInvariant() + }) + }; + } + + [JSInvokable] + public async Task InvokableAsyncMethod(TestDTO dtoViaJson, TestDTO dtoByRef) + { + await Task.Delay(50); + return new object[] + { + new TestDTO // Return via JSON + { + StringVal = dtoViaJson.StringVal.ToUpperInvariant(), + IntVal = dtoViaJson.IntVal * 2, + }, + new DotNetObjectRef(new TestDTO // Return by ref + { + StringVal = dtoByRef.StringVal.ToUpperInvariant(), + IntVal = dtoByRef.IntVal * 2, + }) + }; + } + } + + public class BaseClass + { + public bool DidInvokeMyBaseClassInvocableInstanceVoid; + + [JSInvokable] + public void BaseClassInvokableInstanceVoid() + { + DidInvokeMyBaseClassInvocableInstanceVoid = true; + } + } + + public class DerivedClass : BaseClass + { + } + + public class TestDTO + { + public string StringVal { get; set; } + public int IntVal { get; set; } + } + + public class TestJSRuntime : JSInProcessRuntimeBase + { + private TaskCompletionSource _nextInvocationTcs = new TaskCompletionSource(); + public Task NextInvocationTask => _nextInvocationTcs.Task; + public long LastInvocationAsyncHandle { get; private set; } + public string LastInvocationIdentifier { get; private set; } + public string LastInvocationArgsJson { get; private set; } + + protected override void BeginInvokeJS(long asyncHandle, string identifier, string argsJson) + { + LastInvocationAsyncHandle = asyncHandle; + LastInvocationIdentifier = identifier; + LastInvocationArgsJson = argsJson; + _nextInvocationTcs.SetResult(null); + _nextInvocationTcs = new TaskCompletionSource(); + } + + protected override string InvokeJS(string identifier, string argsJson) + { + LastInvocationAsyncHandle = default; + LastInvocationIdentifier = identifier; + LastInvocationArgsJson = argsJson; + _nextInvocationTcs.SetResult(null); + _nextInvocationTcs = new TaskCompletionSource(); + return null; + } + } + } +} diff --git a/src/JSInterop/test/Microsoft.JSInterop.Test/DotNetObjectRefTest.cs b/src/JSInterop/test/Microsoft.JSInterop.Test/DotNetObjectRefTest.cs new file mode 100644 index 0000000000..969dcae79d --- /dev/null +++ b/src/JSInterop/test/Microsoft.JSInterop.Test/DotNetObjectRefTest.cs @@ -0,0 +1,68 @@ +// 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.Collections.Generic; +using System.Threading.Tasks; +using Xunit; + +namespace Microsoft.JSInterop.Test +{ + public class DotNetObjectRefTest + { + [Fact] + public void CanAccessValue() + { + var obj = new object(); + Assert.Same(obj, new DotNetObjectRef(obj).Value); + } + + [Fact] + public void CanAssociateWithSameRuntimeMultipleTimes() + { + var objRef = new DotNetObjectRef(new object()); + var jsRuntime = new TestJsRuntime(); + objRef.EnsureAttachedToJsRuntime(jsRuntime); + objRef.EnsureAttachedToJsRuntime(jsRuntime); + } + + [Fact] + public void CannotAssociateWithDifferentRuntimes() + { + var objRef = new DotNetObjectRef(new object()); + var jsRuntime1 = new TestJsRuntime(); + var jsRuntime2 = new TestJsRuntime(); + objRef.EnsureAttachedToJsRuntime(jsRuntime1); + + var ex = Assert.Throws( + () => objRef.EnsureAttachedToJsRuntime(jsRuntime2)); + Assert.Contains("Do not attempt to re-use", ex.Message); + } + + [Fact] + public void NotifiesAssociatedJsRuntimeOfDisposal() + { + // Arrange + var objRef = new DotNetObjectRef(new object()); + var jsRuntime = new TestJsRuntime(); + objRef.EnsureAttachedToJsRuntime(jsRuntime); + + // Act + objRef.Dispose(); + + // Assert + Assert.Equal(new[] { objRef }, jsRuntime.UntrackedRefs); + } + + class TestJsRuntime : IJSRuntime + { + public List UntrackedRefs = new List(); + + public Task InvokeAsync(string identifier, params object[] args) + => throw new NotImplementedException(); + + public void UntrackObjectRef(DotNetObjectRef dotNetObjectRef) + => UntrackedRefs.Add(dotNetObjectRef); + } + } +} diff --git a/src/JSInterop/test/Microsoft.JSInterop.Test/JSInProcessRuntimeBaseTest.cs b/src/JSInterop/test/Microsoft.JSInterop.Test/JSInProcessRuntimeBaseTest.cs new file mode 100644 index 0000000000..d2e71f6eb2 --- /dev/null +++ b/src/JSInterop/test/Microsoft.JSInterop.Test/JSInProcessRuntimeBaseTest.cs @@ -0,0 +1,117 @@ +// 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.Collections.Generic; +using System.Linq; +using Xunit; + +namespace Microsoft.JSInterop.Test +{ + public class JSInProcessRuntimeBaseTest + { + [Fact] + public void DispatchesSyncCallsAndDeserializesResults() + { + // Arrange + var runtime = new TestJSInProcessRuntime + { + NextResultJson = Json.Serialize( + new TestDTO { IntValue = 123, StringValue = "Hello" }) + }; + + // Act + var syncResult = runtime.Invoke("test identifier 1", "arg1", 123, true ); + var call = runtime.InvokeCalls.Single(); + + // Assert + Assert.Equal(123, syncResult.IntValue); + Assert.Equal("Hello", syncResult.StringValue); + Assert.Equal("test identifier 1", call.Identifier); + Assert.Equal("[\"arg1\",123,true]", call.ArgsJson); + } + + [Fact] + public void SerializesDotNetObjectWrappersInKnownFormat() + { + // Arrange + var runtime = new TestJSInProcessRuntime { NextResultJson = null }; + var obj1 = new object(); + var obj2 = new object(); + var obj3 = new object(); + + // Act + // Showing we can pass the DotNetObject either as top-level args or nested + var syncResult = runtime.Invoke("test identifier", + new DotNetObjectRef(obj1), + new Dictionary + { + { "obj2", new DotNetObjectRef(obj2) }, + { "obj3", new DotNetObjectRef(obj3) } + }); + + // Assert: Handles null result string + Assert.Null(syncResult); + + // Assert: Serialized as expected + var call = runtime.InvokeCalls.Single(); + Assert.Equal("test identifier", call.Identifier); + Assert.Equal("[\"__dotNetObject:1\",{\"obj2\":\"__dotNetObject:2\",\"obj3\":\"__dotNetObject:3\"}]", call.ArgsJson); + + // Assert: Objects were tracked + Assert.Same(obj1, runtime.ArgSerializerStrategy.FindDotNetObject(1)); + Assert.Same(obj2, runtime.ArgSerializerStrategy.FindDotNetObject(2)); + Assert.Same(obj3, runtime.ArgSerializerStrategy.FindDotNetObject(3)); + } + + [Fact] + public void SyncCallResultCanIncludeDotNetObjects() + { + // Arrange + var runtime = new TestJSInProcessRuntime + { + NextResultJson = "[\"__dotNetObject:2\",\"__dotNetObject:1\"]" + }; + var obj1 = new object(); + var obj2 = new object(); + + // Act + var syncResult = runtime.Invoke("test identifier", + new DotNetObjectRef(obj1), + "some other arg", + new DotNetObjectRef(obj2)); + var call = runtime.InvokeCalls.Single(); + + // Assert + Assert.Equal(new[] { obj2, obj1 }, syncResult); + } + + class TestDTO + { + public int IntValue { get; set; } + public string StringValue { get; set; } + } + + class TestJSInProcessRuntime : JSInProcessRuntimeBase + { + public List InvokeCalls { get; set; } = new List(); + + public string NextResultJson { get; set; } + + protected override string InvokeJS(string identifier, string argsJson) + { + InvokeCalls.Add(new InvokeArgs { Identifier = identifier, ArgsJson = argsJson }); + return NextResultJson; + } + + public class InvokeArgs + { + public string Identifier { get; set; } + public string ArgsJson { get; set; } + } + + protected override void BeginInvokeJS(long asyncHandle, string identifier, string argsJson) + => throw new NotImplementedException("This test only covers sync calls"); + } + } +} diff --git a/src/JSInterop/test/Microsoft.JSInterop.Test/JSRuntimeBaseTest.cs b/src/JSInterop/test/Microsoft.JSInterop.Test/JSRuntimeBaseTest.cs new file mode 100644 index 0000000000..9193d6deb8 --- /dev/null +++ b/src/JSInterop/test/Microsoft.JSInterop.Test/JSRuntimeBaseTest.cs @@ -0,0 +1,191 @@ +// 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 Microsoft.JSInterop.Internal; +using System; +using System.Collections.Generic; +using System.Linq; +using Xunit; + +namespace Microsoft.JSInterop.Test +{ + public class JSRuntimeBaseTest + { + [Fact] + public void DispatchesAsyncCallsWithDistinctAsyncHandles() + { + // Arrange + var runtime = new TestJSRuntime(); + + // Act + runtime.InvokeAsync("test identifier 1", "arg1", 123, true ); + runtime.InvokeAsync("test identifier 2", "some other arg"); + + // Assert + Assert.Collection(runtime.BeginInvokeCalls, + call => + { + Assert.Equal("test identifier 1", call.Identifier); + Assert.Equal("[\"arg1\",123,true]", call.ArgsJson); + }, + call => + { + Assert.Equal("test identifier 2", call.Identifier); + Assert.Equal("[\"some other arg\"]", call.ArgsJson); + Assert.NotEqual(runtime.BeginInvokeCalls[0].AsyncHandle, call.AsyncHandle); + }); + } + + [Fact] + public void CanCompleteAsyncCallsAsSuccess() + { + // Arrange + var runtime = new TestJSRuntime(); + + // Act/Assert: Tasks not initially completed + var unrelatedTask = runtime.InvokeAsync("unrelated call", Array.Empty()); + var task = runtime.InvokeAsync("test identifier", Array.Empty()); + Assert.False(unrelatedTask.IsCompleted); + Assert.False(task.IsCompleted); + + // Act/Assert: Task can be completed + runtime.OnEndInvoke( + runtime.BeginInvokeCalls[1].AsyncHandle, + /* succeeded: */ true, + "my result"); + Assert.False(unrelatedTask.IsCompleted); + Assert.True(task.IsCompleted); + Assert.Equal("my result", task.Result); + } + + [Fact] + public void CanCompleteAsyncCallsAsFailure() + { + // Arrange + var runtime = new TestJSRuntime(); + + // Act/Assert: Tasks not initially completed + var unrelatedTask = runtime.InvokeAsync("unrelated call", Array.Empty()); + var task = runtime.InvokeAsync("test identifier", Array.Empty()); + Assert.False(unrelatedTask.IsCompleted); + Assert.False(task.IsCompleted); + + // Act/Assert: Task can be failed + runtime.OnEndInvoke( + runtime.BeginInvokeCalls[1].AsyncHandle, + /* succeeded: */ false, + "This is a test exception"); + Assert.False(unrelatedTask.IsCompleted); + Assert.True(task.IsCompleted); + + Assert.IsType(task.Exception); + Assert.IsType(task.Exception.InnerException); + Assert.Equal("This is a test exception", ((JSException)task.Exception.InnerException).Message); + } + + [Fact] + public void CannotCompleteSameAsyncCallMoreThanOnce() + { + // Arrange + var runtime = new TestJSRuntime(); + + // Act/Assert + runtime.InvokeAsync("test identifier", Array.Empty()); + var asyncHandle = runtime.BeginInvokeCalls[0].AsyncHandle; + runtime.OnEndInvoke(asyncHandle, true, null); + var ex = Assert.Throws(() => + { + // Second "end invoke" will fail + runtime.OnEndInvoke(asyncHandle, true, null); + }); + Assert.Equal($"There is no pending task with handle '{asyncHandle}'.", ex.Message); + } + + [Fact] + public void SerializesDotNetObjectWrappersInKnownFormat() + { + // Arrange + var runtime = new TestJSRuntime(); + var obj1 = new object(); + var obj2 = new object(); + var obj3 = new object(); + + // Act + // Showing we can pass the DotNetObject either as top-level args or nested + var obj1Ref = new DotNetObjectRef(obj1); + var obj1DifferentRef = new DotNetObjectRef(obj1); + runtime.InvokeAsync("test identifier", + obj1Ref, + new Dictionary + { + { "obj2", new DotNetObjectRef(obj2) }, + { "obj3", new DotNetObjectRef(obj3) }, + { "obj1SameRef", obj1Ref }, + { "obj1DifferentRef", obj1DifferentRef }, + }); + + // Assert: Serialized as expected + var call = runtime.BeginInvokeCalls.Single(); + Assert.Equal("test identifier", call.Identifier); + Assert.Equal("[\"__dotNetObject:1\",{\"obj2\":\"__dotNetObject:2\",\"obj3\":\"__dotNetObject:3\",\"obj1SameRef\":\"__dotNetObject:1\",\"obj1DifferentRef\":\"__dotNetObject:4\"}]", call.ArgsJson); + + // Assert: Objects were tracked + Assert.Same(obj1, runtime.ArgSerializerStrategy.FindDotNetObject(1)); + Assert.Same(obj2, runtime.ArgSerializerStrategy.FindDotNetObject(2)); + Assert.Same(obj3, runtime.ArgSerializerStrategy.FindDotNetObject(3)); + Assert.Same(obj1, runtime.ArgSerializerStrategy.FindDotNetObject(4)); + } + + [Fact] + public void SupportsCustomSerializationForArguments() + { + // Arrange + var runtime = new TestJSRuntime(); + + // Arrange/Act + runtime.InvokeAsync("test identifier", + new WithCustomArgSerializer()); + + // Asssert + var call = runtime.BeginInvokeCalls.Single(); + Assert.Equal("[{\"key1\":\"value1\",\"key2\":123}]", call.ArgsJson); + } + + class TestJSRuntime : JSRuntimeBase + { + public List BeginInvokeCalls = new List(); + + public class BeginInvokeAsyncArgs + { + public long AsyncHandle { get; set; } + public string Identifier { get; set; } + public string ArgsJson { get; set; } + } + + protected override void BeginInvokeJS(long asyncHandle, string identifier, string argsJson) + { + BeginInvokeCalls.Add(new BeginInvokeAsyncArgs + { + AsyncHandle = asyncHandle, + Identifier = identifier, + ArgsJson = argsJson, + }); + } + + public void OnEndInvoke(long asyncHandle, bool succeeded, object resultOrException) + => EndInvokeJS(asyncHandle, succeeded, resultOrException); + } + + class WithCustomArgSerializer : ICustomArgSerializer + { + public object ToJsonPrimitive() + { + return new Dictionary + { + { "key1", "value1" }, + { "key2", 123 }, + }; + } + } + } +} diff --git a/src/JSInterop/test/Microsoft.JSInterop.Test/JSRuntimeTest.cs b/src/JSInterop/test/Microsoft.JSInterop.Test/JSRuntimeTest.cs new file mode 100644 index 0000000000..d5fed45ea4 --- /dev/null +++ b/src/JSInterop/test/Microsoft.JSInterop.Test/JSRuntimeTest.cs @@ -0,0 +1,37 @@ +// 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.Linq; +using System.Threading.Tasks; +using Xunit; + +namespace Microsoft.JSInterop.Test +{ + public class JSRuntimeTest + { + [Fact] + public async Task CanHaveDistinctJSRuntimeInstancesInEachAsyncContext() + { + var tasks = Enumerable.Range(0, 20).Select(async _ => + { + var jsRuntime = new FakeJSRuntime(); + JSRuntime.SetCurrentJSRuntime(jsRuntime); + await Task.Delay(50).ConfigureAwait(false); + Assert.Same(jsRuntime, JSRuntime.Current); + }); + + await Task.WhenAll(tasks); + Assert.Null(JSRuntime.Current); + } + + private class FakeJSRuntime : IJSRuntime + { + public Task InvokeAsync(string identifier, params object[] args) + => throw new NotImplementedException(); + + public void UntrackObjectRef(DotNetObjectRef dotNetObjectRef) + => throw new NotImplementedException(); + } + } +} diff --git a/src/JSInterop/test/Microsoft.JSInterop.Test/JsonUtilTest.cs b/src/JSInterop/test/Microsoft.JSInterop.Test/JsonUtilTest.cs new file mode 100644 index 0000000000..1be98b681e --- /dev/null +++ b/src/JSInterop/test/Microsoft.JSInterop.Test/JsonUtilTest.cs @@ -0,0 +1,349 @@ +// 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 Microsoft.JSInterop.Internal; +using System; +using System.Collections.Generic; +using Xunit; + +namespace Microsoft.JSInterop.Test +{ + public class JsonUtilTest + { + // It's not useful to have a complete set of behavior specifications for + // what the JSON serializer/deserializer does in all cases here. We merely + // expose a simple wrapper over a third-party library that maintains its + // own specs and tests. + // + // We should only add tests here to cover behaviors that Blazor itself + // depends on. + + [Theory] + [InlineData(null, "null")] + [InlineData("My string", "\"My string\"")] + [InlineData(123, "123")] + [InlineData(123.456f, "123.456")] + [InlineData(123.456d, "123.456")] + [InlineData(true, "true")] + public void CanSerializePrimitivesToJson(object value, string expectedJson) + { + Assert.Equal(expectedJson, Json.Serialize(value)); + } + + [Theory] + [InlineData("null", null)] + [InlineData("\"My string\"", "My string")] + [InlineData("123", 123L)] // Would also accept 123 as a System.Int32, but Int64 is fine as a default + [InlineData("123.456", 123.456d)] + [InlineData("true", true)] + public void CanDeserializePrimitivesFromJson(string json, object expectedValue) + { + Assert.Equal(expectedValue, Json.Deserialize(json)); + } + + [Fact] + public void CanSerializeClassToJson() + { + // Arrange + var person = new Person + { + Id = 1844, + Name = "Athos", + Pets = new[] { "Aramis", "Porthos", "D'Artagnan" }, + Hobby = Hobbies.Swordfighting, + SecondaryHobby = Hobbies.Reading, + Nicknames = new List { "Comte de la Fère", "Armand" }, + BirthInstant = new DateTimeOffset(1825, 8, 6, 18, 45, 21, TimeSpan.FromHours(-6)), + Age = new TimeSpan(7665, 1, 30, 0), + Allergies = new Dictionary { { "Ducks", true }, { "Geese", false } }, + }; + + // Act/Assert + Assert.Equal( + "{\"id\":1844,\"name\":\"Athos\",\"pets\":[\"Aramis\",\"Porthos\",\"D'Artagnan\"],\"hobby\":2,\"secondaryHobby\":1,\"nullHobby\":null,\"nicknames\":[\"Comte de la Fère\",\"Armand\"],\"birthInstant\":\"1825-08-06T18:45:21.0000000-06:00\",\"age\":\"7665.01:30:00\",\"allergies\":{\"Ducks\":true,\"Geese\":false}}", + Json.Serialize(person)); + } + + [Fact] + public void CanDeserializeClassFromJson() + { + // Arrange + var json = "{\"id\":1844,\"name\":\"Athos\",\"pets\":[\"Aramis\",\"Porthos\",\"D'Artagnan\"],\"hobby\":2,\"secondaryHobby\":1,\"nullHobby\":null,\"nicknames\":[\"Comte de la Fère\",\"Armand\"],\"birthInstant\":\"1825-08-06T18:45:21.0000000-06:00\",\"age\":\"7665.01:30:00\",\"allergies\":{\"Ducks\":true,\"Geese\":false}}"; + + // Act + var person = Json.Deserialize(json); + + // Assert + Assert.Equal(1844, person.Id); + Assert.Equal("Athos", person.Name); + Assert.Equal(new[] { "Aramis", "Porthos", "D'Artagnan" }, person.Pets); + Assert.Equal(Hobbies.Swordfighting, person.Hobby); + Assert.Equal(Hobbies.Reading, person.SecondaryHobby); + Assert.Null(person.NullHobby); + Assert.Equal(new[] { "Comte de la Fère", "Armand" }, person.Nicknames); + Assert.Equal(new DateTimeOffset(1825, 8, 6, 18, 45, 21, TimeSpan.FromHours(-6)), person.BirthInstant); + Assert.Equal(new TimeSpan(7665, 1, 30, 0), person.Age); + Assert.Equal(new Dictionary { { "Ducks", true }, { "Geese", false } }, person.Allergies); + } + + [Fact] + public void CanDeserializeWithCaseInsensitiveKeys() + { + // Arrange + var json = "{\"ID\":1844,\"NamE\":\"Athos\"}"; + + // Act + var person = Json.Deserialize(json); + + // Assert + Assert.Equal(1844, person.Id); + Assert.Equal("Athos", person.Name); + } + + [Fact] + public void DeserializationPrefersPropertiesOverFields() + { + // Arrange + var json = "{\"member1\":\"Hello\"}"; + + // Act + var person = Json.Deserialize(json); + + // Assert + Assert.Equal("Hello", person.Member1); + Assert.Null(person.member1); + } + + [Fact] + public void CanSerializeStructToJson() + { + // Arrange + var commandResult = new SimpleStruct + { + StringProperty = "Test", + BoolProperty = true, + NullableIntProperty = 1 + }; + + // Act + var result = Json.Serialize(commandResult); + + // Assert + Assert.Equal("{\"stringProperty\":\"Test\",\"boolProperty\":true,\"nullableIntProperty\":1}", result); + } + + [Fact] + public void CanDeserializeStructFromJson() + { + // Arrange + var json = "{\"stringProperty\":\"Test\",\"boolProperty\":true,\"nullableIntProperty\":1}"; + + //Act + var simpleError = Json.Deserialize(json); + + // Assert + Assert.Equal("Test", simpleError.StringProperty); + Assert.True(simpleError.BoolProperty); + Assert.Equal(1, simpleError.NullableIntProperty); + } + + [Fact] + public void CanCreateInstanceOfClassWithPrivateConstructor() + { + // Arrange + var expectedName = "NameValue"; + var json = $"{{\"Name\":\"{expectedName}\"}}"; + + // Act + var instance = Json.Deserialize(json); + + // Assert + Assert.Equal(expectedName, instance.Name); + } + + [Fact] + public void CanSetValueOfPublicPropertiesWithNonPublicSetters() + { + // Arrange + var expectedPrivateValue = "PrivateValue"; + var expectedProtectedValue = "ProtectedValue"; + var expectedInternalValue = "InternalValue"; + + var json = "{" + + $"\"PrivateSetter\":\"{expectedPrivateValue}\"," + + $"\"ProtectedSetter\":\"{expectedProtectedValue}\"," + + $"\"InternalSetter\":\"{expectedInternalValue}\"," + + "}"; + + // Act + var instance = Json.Deserialize(json); + + // Assert + Assert.Equal(expectedPrivateValue, instance.PrivateSetter); + Assert.Equal(expectedProtectedValue, instance.ProtectedSetter); + Assert.Equal(expectedInternalValue, instance.InternalSetter); + } + + [Fact] + public void RejectsTypesWithAmbiguouslyNamedProperties() + { + var ex = Assert.Throws(() => + { + Json.Deserialize("{}"); + }); + + Assert.Equal($"The type '{typeof(ClashingProperties).FullName}' contains multiple public properties " + + $"with names case-insensitively matching '{nameof(ClashingProperties.PROP1).ToLowerInvariant()}'. " + + $"Such types cannot be used for JSON deserialization.", + ex.Message); + } + + [Fact] + public void RejectsTypesWithAmbiguouslyNamedFields() + { + var ex = Assert.Throws(() => + { + Json.Deserialize("{}"); + }); + + Assert.Equal($"The type '{typeof(ClashingFields).FullName}' contains multiple public fields " + + $"with names case-insensitively matching '{nameof(ClashingFields.Field1).ToLowerInvariant()}'. " + + $"Such types cannot be used for JSON deserialization.", + ex.Message); + } + + [Fact] + public void NonEmptyConstructorThrowsUsefulException() + { + // Arrange + var json = "{\"Property\":1}"; + var type = typeof(NonEmptyConstructorPoco); + + // Act + var exception = Assert.Throws(() => + { + Json.Deserialize(json); + }); + + // Assert + Assert.Equal( + $"Cannot deserialize JSON into type '{type.FullName}' because it does not have a public parameterless constructor.", + exception.Message); + } + + // Test cases based on https://github.com/JamesNK/Newtonsoft.Json/blob/122afba9908832bd5ac207164ee6c303bfd65cf1/Src/Newtonsoft.Json.Tests/Utilities/StringUtilsTests.cs#L41 + // The only difference is that our logic doesn't have to handle space-separated words, + // because we're only use this for camelcasing .NET member names + // + // Not all of the following cases are really valid .NET member names, but we have no reason + // to implement more logic to detect invalid member names besides the basics (null or empty). + [Theory] + [InlineData("URLValue", "urlValue")] + [InlineData("URL", "url")] + [InlineData("ID", "id")] + [InlineData("I", "i")] + [InlineData("Person", "person")] + [InlineData("xPhone", "xPhone")] + [InlineData("XPhone", "xPhone")] + [InlineData("X_Phone", "x_Phone")] + [InlineData("X__Phone", "x__Phone")] + [InlineData("IsCIA", "isCIA")] + [InlineData("VmQ", "vmQ")] + [InlineData("Xml2Json", "xml2Json")] + [InlineData("SnAkEcAsE", "snAkEcAsE")] + [InlineData("SnA__kEcAsE", "snA__kEcAsE")] + [InlineData("already_snake_case_", "already_snake_case_")] + [InlineData("IsJSONProperty", "isJSONProperty")] + [InlineData("SHOUTING_CASE", "shoutinG_CASE")] + [InlineData("9999-12-31T23:59:59.9999999Z", "9999-12-31T23:59:59.9999999Z")] + [InlineData("Hi!! This is text. Time to test.", "hi!! This is text. Time to test.")] + [InlineData("BUILDING", "building")] + [InlineData("BUILDINGProperty", "buildingProperty")] + public void MemberNameToCamelCase_Valid(string input, string expectedOutput) + { + Assert.Equal(expectedOutput, CamelCase.MemberNameToCamelCase(input)); + } + + [Theory] + [InlineData("")] + [InlineData(null)] + public void MemberNameToCamelCase_Invalid(string input) + { + var ex = Assert.Throws(() => + CamelCase.MemberNameToCamelCase(input)); + Assert.Equal("value", ex.ParamName); + Assert.StartsWith($"The value '{input ?? "null"}' is not a valid member name.", ex.Message); + } + + class NonEmptyConstructorPoco + { + public NonEmptyConstructorPoco(int parameter) {} + + public int Property { get; set; } + } + + struct SimpleStruct + { + public string StringProperty { get; set; } + public bool BoolProperty { get; set; } + public int? NullableIntProperty { get; set; } + } + + class Person + { + public int Id { get; set; } + public string Name { get; set; } + public string[] Pets { get; set; } + public Hobbies Hobby { get; set; } + public Hobbies? SecondaryHobby { get; set; } + public Hobbies? NullHobby { get; set; } + public IList Nicknames { get; set; } + public DateTimeOffset BirthInstant { get; set; } + public TimeSpan Age { get; set; } + public IDictionary Allergies { get; set; } + } + + enum Hobbies { Reading = 1, Swordfighting = 2 } + +#pragma warning disable 0649 + class ClashingProperties + { + public string Prop1 { get; set; } + public int PROP1 { get; set; } + } + + class ClashingFields + { + public string Field1; + public int field1; + } + + class PrefersPropertiesOverFields + { + public string member1; + public string Member1 { get; set; } + } +#pragma warning restore 0649 + + class PrivateConstructor + { + public string Name { get; set; } + + private PrivateConstructor() + { + } + + public PrivateConstructor(string name) + { + Name = name; + } + } + + class NonPublicSetterOnPublicProperty + { + public string PrivateSetter { get; private set; } + public string ProtectedSetter { get; protected set; } + public string InternalSetter { get; internal set; } + } + } +} diff --git a/src/JSInterop/test/Microsoft.JSInterop.Test/Microsoft.JSInterop.Test.csproj b/src/JSInterop/test/Microsoft.JSInterop.Test/Microsoft.JSInterop.Test.csproj new file mode 100644 index 0000000000..893a11d33a --- /dev/null +++ b/src/JSInterop/test/Microsoft.JSInterop.Test/Microsoft.JSInterop.Test.csproj @@ -0,0 +1,20 @@ + + + + netcoreapp2.1 + false + 7.3 + + + + + + + + + + + + + + From 4de3b5e105caf3b9598a7f476d7ca9e244c5b28d Mon Sep 17 00:00:00 2001 From: Chris Ross Date: Wed, 9 Jan 2019 13:38:12 -0800 Subject: [PATCH 0046/1101] Move the HostFactoryResolver to Extensions (dotnet/extensions#925) \n\nCommit migrated from https://github.com/dotnet/extensions/commit/9cd9cea3983ec3421038c4b540161f0686273754 --- .../HostFactoryResolver.cs | 112 +++++++++++++++++ .../Shared.Tests/HostFactoryResolverTests.cs | 116 ++++++++++++++++++ .../Microsoft.AspNetCore.Shared.Tests.csproj | 10 ++ .../BuildWebHostInvalidSignature.csproj | 12 ++ .../BuildWebHostInvalidSignature/Program.cs | 17 +++ .../BuildWebHostPatternTestSite.csproj | 12 ++ .../BuildWebHostPatternTestSite/Program.cs | 16 +++ .../CreateHostBuilderInvalidSignature.csproj | 12 ++ .../Program.cs | 18 +++ .../CreateHostBuilderPatternTestSite.csproj | 12 ++ .../Program.cs | 19 +++ ...reateWebHostBuilderInvalidSignature.csproj | 12 ++ .../Program.cs | 17 +++ ...CreateWebHostBuilderPatternTestSite.csproj | 12 ++ .../Program.cs | 19 +++ .../test/testassets/MockHostTypes/Host.cs | 12 ++ .../testassets/MockHostTypes/HostBuilder.cs | 10 ++ .../test/testassets/MockHostTypes/IHost.cs | 12 ++ .../testassets/MockHostTypes/IHostBuilder.cs | 10 ++ .../test/testassets/MockHostTypes/IWebHost.cs | 12 ++ .../MockHostTypes/IWebHostBuilder.cs | 10 ++ .../MockHostTypes/MockHostTypes.csproj | 7 ++ .../MockHostTypes/ServiceProvider.cs | 12 ++ .../test/testassets/MockHostTypes/WebHost.cs | 12 ++ .../MockHostTypes/WebHostBuilder.cs | 10 ++ 25 files changed, 523 insertions(+) create mode 100644 src/Shared/HostFactoryResolver/HostFactoryResolver.cs create mode 100644 src/Shared/test/Shared.Tests/HostFactoryResolverTests.cs create mode 100644 src/Shared/test/testassets/BuildWebHostInvalidSignature/BuildWebHostInvalidSignature.csproj create mode 100644 src/Shared/test/testassets/BuildWebHostInvalidSignature/Program.cs create mode 100644 src/Shared/test/testassets/BuildWebHostPatternTestSite/BuildWebHostPatternTestSite.csproj create mode 100644 src/Shared/test/testassets/BuildWebHostPatternTestSite/Program.cs create mode 100644 src/Shared/test/testassets/CreateHostBuilderInvalidSignature/CreateHostBuilderInvalidSignature.csproj create mode 100644 src/Shared/test/testassets/CreateHostBuilderInvalidSignature/Program.cs create mode 100644 src/Shared/test/testassets/CreateHostBuilderPatternTestSite/CreateHostBuilderPatternTestSite.csproj create mode 100644 src/Shared/test/testassets/CreateHostBuilderPatternTestSite/Program.cs create mode 100644 src/Shared/test/testassets/CreateWebHostBuilderInvalidSignature/CreateWebHostBuilderInvalidSignature.csproj create mode 100644 src/Shared/test/testassets/CreateWebHostBuilderInvalidSignature/Program.cs create mode 100644 src/Shared/test/testassets/CreateWebHostBuilderPatternTestSite/CreateWebHostBuilderPatternTestSite.csproj create mode 100644 src/Shared/test/testassets/CreateWebHostBuilderPatternTestSite/Program.cs create mode 100644 src/Shared/test/testassets/MockHostTypes/Host.cs create mode 100644 src/Shared/test/testassets/MockHostTypes/HostBuilder.cs create mode 100644 src/Shared/test/testassets/MockHostTypes/IHost.cs create mode 100644 src/Shared/test/testassets/MockHostTypes/IHostBuilder.cs create mode 100644 src/Shared/test/testassets/MockHostTypes/IWebHost.cs create mode 100644 src/Shared/test/testassets/MockHostTypes/IWebHostBuilder.cs create mode 100644 src/Shared/test/testassets/MockHostTypes/MockHostTypes.csproj create mode 100644 src/Shared/test/testassets/MockHostTypes/ServiceProvider.cs create mode 100644 src/Shared/test/testassets/MockHostTypes/WebHost.cs create mode 100644 src/Shared/test/testassets/MockHostTypes/WebHostBuilder.cs diff --git a/src/Shared/HostFactoryResolver/HostFactoryResolver.cs b/src/Shared/HostFactoryResolver/HostFactoryResolver.cs new file mode 100644 index 0000000000..cb9f811237 --- /dev/null +++ b/src/Shared/HostFactoryResolver/HostFactoryResolver.cs @@ -0,0 +1,112 @@ +// 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.Reflection; + +namespace Microsoft.Extensions.Hosting +{ + internal class HostFactoryResolver + { + public static readonly string BuildWebHost = nameof(BuildWebHost); + public static readonly string CreateWebHostBuilder = nameof(CreateWebHostBuilder); + public static readonly string CreateHostBuilder = nameof(CreateHostBuilder); + + public static Func ResolveWebHostFactory(Assembly assembly) + { + return ResolveFactory(assembly, BuildWebHost); + } + + public static Func ResolveWebHostBuilderFactory(Assembly assembly) + { + return ResolveFactory(assembly, CreateWebHostBuilder); + } + + public static Func ResolveHostBuilderFactory(Assembly assembly) + { + return ResolveFactory(assembly, CreateHostBuilder); + } + + private static Func ResolveFactory(Assembly assembly, string name) + { + var programType = assembly?.EntryPoint?.DeclaringType; + if (programType == null) + { + return null; + } + + var factory = programType.GetTypeInfo().GetDeclaredMethod(name); + if (!IsFactory(factory)) + { + return null; + } + + return args => (T)factory.Invoke(null, new object[] { args }); + } + + // TReturn Factory(string[] args); + private static bool IsFactory(MethodInfo factory) + { + return factory != null + && typeof(TReturn).IsAssignableFrom(factory.ReturnType) + && factory.GetParameters().Length == 1 + && typeof(string[]).Equals(factory.GetParameters()[0].ParameterType); + } + + // Used by EF tooling without any Hosting references. Looses some return type safety checks. + public static Func ResolveServiceProviderFactory(Assembly assembly) + { + // Prefer the older patterns by default for back compat. + var webHostFactory = ResolveWebHostFactory(assembly); + if (webHostFactory != null) + { + return args => + { + var webHost = webHostFactory(args); + return GetServiceProvider(webHost); + }; + } + + var webHostBuilderFactory = ResolveWebHostBuilderFactory(assembly); + if (webHostBuilderFactory != null) + { + return args => + { + var webHostBuilder = webHostBuilderFactory(args); + var webHost = Build(webHostBuilder); + return GetServiceProvider(webHost); + }; + } + + var hostBuilderFactory = ResolveHostBuilderFactory(assembly); + if (hostBuilderFactory != null) + { + return args => + { + var hostBuilder = hostBuilderFactory(args); + var host = Build(hostBuilder); + return GetServiceProvider(host); + }; + } + + return null; + } + + private static object Build(object builder) + { + var buildMethod = builder.GetType().GetMethod("Build"); + return buildMethod?.Invoke(builder, Array.Empty()); + } + + private static IServiceProvider GetServiceProvider(object host) + { + if (host == null) + { + return null; + } + var hostType = host.GetType(); + var servicesProperty = hostType.GetTypeInfo().GetDeclaredProperty("Services"); + return (IServiceProvider)servicesProperty.GetValue(host); + } + } +} diff --git a/src/Shared/test/Shared.Tests/HostFactoryResolverTests.cs b/src/Shared/test/Shared.Tests/HostFactoryResolverTests.cs new file mode 100644 index 0000000000..a26fb7b133 --- /dev/null +++ b/src/Shared/test/Shared.Tests/HostFactoryResolverTests.cs @@ -0,0 +1,116 @@ +// 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 MockHostTypes; +using System; +using Xunit; + +namespace Microsoft.Extensions.Hosting.Tests +{ + public class HostFactoryResolverTests + { + [Fact] + public void BuildWebHostPattern_CanFindWebHost() + { + var factory = HostFactoryResolver.ResolveWebHostFactory(typeof(BuildWebHostPatternTestSite.Program).Assembly); + + Assert.NotNull(factory); + Assert.IsAssignableFrom(factory(Array.Empty())); + } + + [Fact] + public void BuildWebHostPattern_CanFindServiceProvider() + { + var factory = HostFactoryResolver.ResolveServiceProviderFactory(typeof(BuildWebHostPatternTestSite.Program).Assembly); + + Assert.NotNull(factory); + Assert.IsAssignableFrom(factory(Array.Empty())); + } + + [Fact] + public void BuildWebHostPattern__Invalid_CantFindWebHost() + { + var factory = HostFactoryResolver.ResolveWebHostFactory(typeof(BuildWebHostInvalidSignature.Program).Assembly); + + Assert.Null(factory); + } + + [Fact] + public void BuildWebHostPattern__Invalid_CantFindServiceProvider() + { + var factory = HostFactoryResolver.ResolveServiceProviderFactory(typeof(BuildWebHostInvalidSignature.Program).Assembly); + + Assert.Null(factory); + } + + [Fact] + public void CreateWebHostBuilderPattern_CanFindWebHostBuilder() + { + var factory = HostFactoryResolver.ResolveWebHostBuilderFactory(typeof(CreateWebHostBuilderPatternTestSite.Program).Assembly); + + Assert.NotNull(factory); + Assert.IsAssignableFrom(factory(Array.Empty())); + } + + [Fact] + public void CreateWebHostBuilderPattern_CanFindServiceProvider() + { + var factory = HostFactoryResolver.ResolveServiceProviderFactory(typeof(CreateWebHostBuilderPatternTestSite.Program).Assembly); + + Assert.NotNull(factory); + Assert.IsAssignableFrom(factory(Array.Empty())); + } + + [Fact] + public void CreateWebHostBuilderPattern__Invalid_CantFindWebHostBuilder() + { + var factory = HostFactoryResolver.ResolveWebHostBuilderFactory(typeof(CreateWebHostBuilderInvalidSignature.Program).Assembly); + + Assert.Null(factory); + } + + [Fact] + public void CreateWebHostBuilderPattern__InvalidReturnType_CanFindServiceProvider() + { + var factory = HostFactoryResolver.ResolveServiceProviderFactory(typeof(CreateWebHostBuilderInvalidSignature.Program).Assembly); + + Assert.NotNull(factory); + Assert.Null(factory(Array.Empty())); + + } + + [Fact] + public void CreateHostBuilderPattern_CanFindHostBuilder() + { + var factory = HostFactoryResolver.ResolveHostBuilderFactory(typeof(CreateHostBuilderPatternTestSite.Program).Assembly); + + Assert.NotNull(factory); + Assert.IsAssignableFrom(factory(Array.Empty())); + } + + [Fact] + public void CreateHostBuilderPattern_CanFindServiceProvider() + { + var factory = HostFactoryResolver.ResolveServiceProviderFactory(typeof(CreateHostBuilderPatternTestSite.Program).Assembly); + + Assert.NotNull(factory); + Assert.IsAssignableFrom(factory(Array.Empty())); + } + + [Fact] + public void CreateHostBuilderPattern__Invalid_CantFindHostBuilder() + { + var factory = HostFactoryResolver.ResolveHostBuilderFactory(typeof(CreateHostBuilderInvalidSignature.Program).Assembly); + + Assert.Null(factory); + } + + [Fact] + public void CreateHostBuilderPattern__Invalid_CantFindServiceProvider() + { + var factory = HostFactoryResolver.ResolveServiceProviderFactory(typeof(CreateHostBuilderInvalidSignature.Program).Assembly); + + Assert.Null(factory); + } + } +} diff --git a/src/Shared/test/Shared.Tests/Microsoft.AspNetCore.Shared.Tests.csproj b/src/Shared/test/Shared.Tests/Microsoft.AspNetCore.Shared.Tests.csproj index a5950b7fc9..222c88bc67 100644 --- a/src/Shared/test/Shared.Tests/Microsoft.AspNetCore.Shared.Tests.csproj +++ b/src/Shared/test/Shared.Tests/Microsoft.AspNetCore.Shared.Tests.csproj @@ -13,6 +13,16 @@ " /> + + + + + + + + + + diff --git a/src/Shared/test/testassets/BuildWebHostInvalidSignature/BuildWebHostInvalidSignature.csproj b/src/Shared/test/testassets/BuildWebHostInvalidSignature/BuildWebHostInvalidSignature.csproj new file mode 100644 index 0000000000..6368289f65 --- /dev/null +++ b/src/Shared/test/testassets/BuildWebHostInvalidSignature/BuildWebHostInvalidSignature.csproj @@ -0,0 +1,12 @@ + + + + netcoreapp3.0;net472 + Exe + + + + + + + diff --git a/src/Shared/test/testassets/BuildWebHostInvalidSignature/Program.cs b/src/Shared/test/testassets/BuildWebHostInvalidSignature/Program.cs new file mode 100644 index 0000000000..ba9e3dab6a --- /dev/null +++ b/src/Shared/test/testassets/BuildWebHostInvalidSignature/Program.cs @@ -0,0 +1,17 @@ +// 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 MockHostTypes; + +namespace BuildWebHostInvalidSignature +{ + public class Program + { + static void Main(string[] args) + { + } + + // Missing string[] args + public static IWebHost BuildWebHost() => null; + } +} diff --git a/src/Shared/test/testassets/BuildWebHostPatternTestSite/BuildWebHostPatternTestSite.csproj b/src/Shared/test/testassets/BuildWebHostPatternTestSite/BuildWebHostPatternTestSite.csproj new file mode 100644 index 0000000000..6368289f65 --- /dev/null +++ b/src/Shared/test/testassets/BuildWebHostPatternTestSite/BuildWebHostPatternTestSite.csproj @@ -0,0 +1,12 @@ + + + + netcoreapp3.0;net472 + Exe + + + + + + + diff --git a/src/Shared/test/testassets/BuildWebHostPatternTestSite/Program.cs b/src/Shared/test/testassets/BuildWebHostPatternTestSite/Program.cs new file mode 100644 index 0000000000..b1d0655e4d --- /dev/null +++ b/src/Shared/test/testassets/BuildWebHostPatternTestSite/Program.cs @@ -0,0 +1,16 @@ +// 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 MockHostTypes; + +namespace BuildWebHostPatternTestSite +{ + public class Program + { + static void Main(string[] args) + { + } + + public static IWebHost BuildWebHost(string[] args) => new WebHost(); + } +} diff --git a/src/Shared/test/testassets/CreateHostBuilderInvalidSignature/CreateHostBuilderInvalidSignature.csproj b/src/Shared/test/testassets/CreateHostBuilderInvalidSignature/CreateHostBuilderInvalidSignature.csproj new file mode 100644 index 0000000000..6368289f65 --- /dev/null +++ b/src/Shared/test/testassets/CreateHostBuilderInvalidSignature/CreateHostBuilderInvalidSignature.csproj @@ -0,0 +1,12 @@ + + + + netcoreapp3.0;net472 + Exe + + + + + + + diff --git a/src/Shared/test/testassets/CreateHostBuilderInvalidSignature/Program.cs b/src/Shared/test/testassets/CreateHostBuilderInvalidSignature/Program.cs new file mode 100644 index 0000000000..8451301a20 --- /dev/null +++ b/src/Shared/test/testassets/CreateHostBuilderInvalidSignature/Program.cs @@ -0,0 +1,18 @@ +// 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 MockHostTypes; + +namespace CreateHostBuilderInvalidSignature +{ + public class Program + { + public static void Main(string[] args) + { + var webHost = CreateHostBuilder(null, args).Build(); + } + + // Extra parameter + private static IHostBuilder CreateHostBuilder(object extraParam, string[] args) => null; + } +} diff --git a/src/Shared/test/testassets/CreateHostBuilderPatternTestSite/CreateHostBuilderPatternTestSite.csproj b/src/Shared/test/testassets/CreateHostBuilderPatternTestSite/CreateHostBuilderPatternTestSite.csproj new file mode 100644 index 0000000000..6368289f65 --- /dev/null +++ b/src/Shared/test/testassets/CreateHostBuilderPatternTestSite/CreateHostBuilderPatternTestSite.csproj @@ -0,0 +1,12 @@ + + + + netcoreapp3.0;net472 + Exe + + + + + + + diff --git a/src/Shared/test/testassets/CreateHostBuilderPatternTestSite/Program.cs b/src/Shared/test/testassets/CreateHostBuilderPatternTestSite/Program.cs new file mode 100644 index 0000000000..70edf16097 --- /dev/null +++ b/src/Shared/test/testassets/CreateHostBuilderPatternTestSite/Program.cs @@ -0,0 +1,19 @@ +// 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 MockHostTypes; + +namespace CreateHostBuilderPatternTestSite +{ + public class Program + { + public static void Main(string[] args) + { + var webHost = CreateHostBuilder(args).Build(); + } + + // Do not change the signature of this method. It's used for tests. + private static HostBuilder CreateHostBuilder(string[] args) => + new HostBuilder(); + } +} diff --git a/src/Shared/test/testassets/CreateWebHostBuilderInvalidSignature/CreateWebHostBuilderInvalidSignature.csproj b/src/Shared/test/testassets/CreateWebHostBuilderInvalidSignature/CreateWebHostBuilderInvalidSignature.csproj new file mode 100644 index 0000000000..6368289f65 --- /dev/null +++ b/src/Shared/test/testassets/CreateWebHostBuilderInvalidSignature/CreateWebHostBuilderInvalidSignature.csproj @@ -0,0 +1,12 @@ + + + + netcoreapp3.0;net472 + Exe + + + + + + + diff --git a/src/Shared/test/testassets/CreateWebHostBuilderInvalidSignature/Program.cs b/src/Shared/test/testassets/CreateWebHostBuilderInvalidSignature/Program.cs new file mode 100644 index 0000000000..1533acbf57 --- /dev/null +++ b/src/Shared/test/testassets/CreateWebHostBuilderInvalidSignature/Program.cs @@ -0,0 +1,17 @@ +// 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 MockHostTypes; + +namespace CreateWebHostBuilderInvalidSignature +{ + public class Program + { + static void Main(string[] args) + { + } + + // Wrong return type + public static IWebHost CreateWebHostBuilder(string[] args) => new WebHost(); + } +} diff --git a/src/Shared/test/testassets/CreateWebHostBuilderPatternTestSite/CreateWebHostBuilderPatternTestSite.csproj b/src/Shared/test/testassets/CreateWebHostBuilderPatternTestSite/CreateWebHostBuilderPatternTestSite.csproj new file mode 100644 index 0000000000..6368289f65 --- /dev/null +++ b/src/Shared/test/testassets/CreateWebHostBuilderPatternTestSite/CreateWebHostBuilderPatternTestSite.csproj @@ -0,0 +1,12 @@ + + + + netcoreapp3.0;net472 + Exe + + + + + + + diff --git a/src/Shared/test/testassets/CreateWebHostBuilderPatternTestSite/Program.cs b/src/Shared/test/testassets/CreateWebHostBuilderPatternTestSite/Program.cs new file mode 100644 index 0000000000..caab3cb224 --- /dev/null +++ b/src/Shared/test/testassets/CreateWebHostBuilderPatternTestSite/Program.cs @@ -0,0 +1,19 @@ +// 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 MockHostTypes; + +namespace CreateWebHostBuilderPatternTestSite +{ + public class Program + { + public static void Main(string[] args) + { + var webHost = CreateWebHostBuilder(args).Build(); + } + + // Do not change the signature of this method. It's used for tests. + private static IWebHostBuilder CreateWebHostBuilder(string[] args) => + new WebHostBuilder(); + } +} diff --git a/src/Shared/test/testassets/MockHostTypes/Host.cs b/src/Shared/test/testassets/MockHostTypes/Host.cs new file mode 100644 index 0000000000..412ab63ef3 --- /dev/null +++ b/src/Shared/test/testassets/MockHostTypes/Host.cs @@ -0,0 +1,12 @@ +// 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; + +namespace MockHostTypes +{ + public class Host : IHost + { + public IServiceProvider Services { get; } = new ServiceProvider(); + } +} diff --git a/src/Shared/test/testassets/MockHostTypes/HostBuilder.cs b/src/Shared/test/testassets/MockHostTypes/HostBuilder.cs new file mode 100644 index 0000000000..eb62e9a4b1 --- /dev/null +++ b/src/Shared/test/testassets/MockHostTypes/HostBuilder.cs @@ -0,0 +1,10 @@ +// 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. + +namespace MockHostTypes +{ + public class HostBuilder : IHostBuilder + { + public IHost Build() => new Host(); + } +} diff --git a/src/Shared/test/testassets/MockHostTypes/IHost.cs b/src/Shared/test/testassets/MockHostTypes/IHost.cs new file mode 100644 index 0000000000..27c6dbaf71 --- /dev/null +++ b/src/Shared/test/testassets/MockHostTypes/IHost.cs @@ -0,0 +1,12 @@ +// 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; + +namespace MockHostTypes +{ + public interface IHost + { + IServiceProvider Services { get; } + } +} diff --git a/src/Shared/test/testassets/MockHostTypes/IHostBuilder.cs b/src/Shared/test/testassets/MockHostTypes/IHostBuilder.cs new file mode 100644 index 0000000000..2053b52106 --- /dev/null +++ b/src/Shared/test/testassets/MockHostTypes/IHostBuilder.cs @@ -0,0 +1,10 @@ +// 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. + +namespace MockHostTypes +{ + public interface IHostBuilder + { + IHost Build(); + } +} diff --git a/src/Shared/test/testassets/MockHostTypes/IWebHost.cs b/src/Shared/test/testassets/MockHostTypes/IWebHost.cs new file mode 100644 index 0000000000..f93bba440c --- /dev/null +++ b/src/Shared/test/testassets/MockHostTypes/IWebHost.cs @@ -0,0 +1,12 @@ +// 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; + +namespace MockHostTypes +{ + public interface IWebHost + { + IServiceProvider Services { get; } + } +} diff --git a/src/Shared/test/testassets/MockHostTypes/IWebHostBuilder.cs b/src/Shared/test/testassets/MockHostTypes/IWebHostBuilder.cs new file mode 100644 index 0000000000..1159ae103e --- /dev/null +++ b/src/Shared/test/testassets/MockHostTypes/IWebHostBuilder.cs @@ -0,0 +1,10 @@ +// 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. + +namespace MockHostTypes +{ + public interface IWebHostBuilder + { + IWebHost Build(); + } +} diff --git a/src/Shared/test/testassets/MockHostTypes/MockHostTypes.csproj b/src/Shared/test/testassets/MockHostTypes/MockHostTypes.csproj new file mode 100644 index 0000000000..3272f8d93a --- /dev/null +++ b/src/Shared/test/testassets/MockHostTypes/MockHostTypes.csproj @@ -0,0 +1,7 @@ + + + + netcoreapp3.0;net472 + + + diff --git a/src/Shared/test/testassets/MockHostTypes/ServiceProvider.cs b/src/Shared/test/testassets/MockHostTypes/ServiceProvider.cs new file mode 100644 index 0000000000..7b550c9d32 --- /dev/null +++ b/src/Shared/test/testassets/MockHostTypes/ServiceProvider.cs @@ -0,0 +1,12 @@ +// 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; + +namespace MockHostTypes +{ + public class ServiceProvider : IServiceProvider + { + public object GetService(Type serviceType) => null; + } +} diff --git a/src/Shared/test/testassets/MockHostTypes/WebHost.cs b/src/Shared/test/testassets/MockHostTypes/WebHost.cs new file mode 100644 index 0000000000..77d3d58ca4 --- /dev/null +++ b/src/Shared/test/testassets/MockHostTypes/WebHost.cs @@ -0,0 +1,12 @@ +// 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; + +namespace MockHostTypes +{ + public class WebHost : IWebHost + { + public IServiceProvider Services { get; } = new ServiceProvider(); + } +} diff --git a/src/Shared/test/testassets/MockHostTypes/WebHostBuilder.cs b/src/Shared/test/testassets/MockHostTypes/WebHostBuilder.cs new file mode 100644 index 0000000000..216fb28d60 --- /dev/null +++ b/src/Shared/test/testassets/MockHostTypes/WebHostBuilder.cs @@ -0,0 +1,10 @@ +// 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. + +namespace MockHostTypes +{ + public class WebHostBuilder : IWebHostBuilder + { + public IWebHost Build() => new WebHost(); + } +} From 33ee3dc379005a1c465b5ff7c8b8f89e4607e56e Mon Sep 17 00:00:00 2001 From: Pranav K Date: Wed, 9 Jan 2019 11:21:29 -0800 Subject: [PATCH 0047/1101] Refactoring to do KoreBuild \ mondorepo \n\nCommit migrated from https://github.com/dotnet/extensions/commit/3a884e2e1e6ae3f1fc4381fc8fbb428de5cff063 --- src/JSInterop/.editorconfig | 27 --------- src/JSInterop/Directory.Build.props | 7 +++ .../src}/Microsoft.JSInterop.JS.csproj | 2 +- .../src}/package-lock.json | 0 .../src}/package.json | 0 .../src}/src/Microsoft.JSInterop.ts | 0 .../src}/tsconfig.json | 0 src/JSInterop/Microsoft.JSInterop.sln | 58 ------------------- .../src}/DotNetDispatcher.cs | 0 .../src}/DotNetObjectRef.cs | 0 .../src}/ICustomArgSerializer.cs | 0 .../src}/IJSInProcessRuntime.cs | 0 .../src}/IJSRuntime.cs | 0 .../src}/InteropArgSerializerStrategy.cs | 0 .../src}/JSAsyncCallResult.cs | 0 .../src}/JSException.cs | 0 .../src}/JSInProcessRuntimeBase.cs | 0 .../src}/JSInvokableAttribute.cs | 0 .../src}/JSRuntime.cs | 0 .../src}/JSRuntimeBase.cs | 0 .../src}/Json/CamelCase.cs | 0 .../src}/Json/Json.cs | 0 .../src}/Json/SimpleJson/README.txt | 0 .../src}/Json/SimpleJson/SimpleJson.cs | 0 .../src/Microsoft.JSInterop.csproj | 10 ++++ .../src}/Properties/AssemblyInfo.cs | 0 .../src}/TaskGenericsUtil.cs | 0 .../test}/DotNetDispatcherTest.cs | 0 .../test}/DotNetObjectRefTest.cs | 0 .../test}/JSInProcessRuntimeBaseTest.cs | 0 .../test}/JSRuntimeBaseTest.cs | 0 .../test}/JSRuntimeTest.cs | 0 .../test}/JsonUtilTest.cs | 0 .../test/Microsoft.JSInterop.Test.csproj | 11 ++++ .../src}/InternalCalls.cs | 0 .../src/Mono.WebAssembly.Interop.csproj | 14 +++++ .../src}/MonoWebAssemblyJSRuntime.cs | 0 .../src/Microsoft.JSInterop.JS/.gitignore | 1 - .../Microsoft.JSInterop.csproj | 7 --- .../Mono.WebAssembly.Interop.csproj | 11 ---- .../Microsoft.JSInterop.Test.csproj | 20 ------- 41 files changed, 43 insertions(+), 125 deletions(-) delete mode 100644 src/JSInterop/.editorconfig create mode 100644 src/JSInterop/Directory.Build.props rename src/JSInterop/{src/Microsoft.JSInterop.JS => Microsoft.JSInterop.JS/src}/Microsoft.JSInterop.JS.csproj (76%) rename src/JSInterop/{src/Microsoft.JSInterop.JS => Microsoft.JSInterop.JS/src}/package-lock.json (100%) rename src/JSInterop/{src/Microsoft.JSInterop.JS => Microsoft.JSInterop.JS/src}/package.json (100%) rename src/JSInterop/{src/Microsoft.JSInterop.JS => Microsoft.JSInterop.JS/src}/src/Microsoft.JSInterop.ts (100%) rename src/JSInterop/{src/Microsoft.JSInterop.JS => Microsoft.JSInterop.JS/src}/tsconfig.json (100%) delete mode 100644 src/JSInterop/Microsoft.JSInterop.sln rename src/JSInterop/{src/Microsoft.JSInterop => Microsoft.JSInterop/src}/DotNetDispatcher.cs (100%) rename src/JSInterop/{src/Microsoft.JSInterop => Microsoft.JSInterop/src}/DotNetObjectRef.cs (100%) rename src/JSInterop/{src/Microsoft.JSInterop => Microsoft.JSInterop/src}/ICustomArgSerializer.cs (100%) rename src/JSInterop/{src/Microsoft.JSInterop => Microsoft.JSInterop/src}/IJSInProcessRuntime.cs (100%) rename src/JSInterop/{src/Microsoft.JSInterop => Microsoft.JSInterop/src}/IJSRuntime.cs (100%) rename src/JSInterop/{src/Microsoft.JSInterop => Microsoft.JSInterop/src}/InteropArgSerializerStrategy.cs (100%) rename src/JSInterop/{src/Microsoft.JSInterop => Microsoft.JSInterop/src}/JSAsyncCallResult.cs (100%) rename src/JSInterop/{src/Microsoft.JSInterop => Microsoft.JSInterop/src}/JSException.cs (100%) rename src/JSInterop/{src/Microsoft.JSInterop => Microsoft.JSInterop/src}/JSInProcessRuntimeBase.cs (100%) rename src/JSInterop/{src/Microsoft.JSInterop => Microsoft.JSInterop/src}/JSInvokableAttribute.cs (100%) rename src/JSInterop/{src/Microsoft.JSInterop => Microsoft.JSInterop/src}/JSRuntime.cs (100%) rename src/JSInterop/{src/Microsoft.JSInterop => Microsoft.JSInterop/src}/JSRuntimeBase.cs (100%) rename src/JSInterop/{src/Microsoft.JSInterop => Microsoft.JSInterop/src}/Json/CamelCase.cs (100%) rename src/JSInterop/{src/Microsoft.JSInterop => Microsoft.JSInterop/src}/Json/Json.cs (100%) rename src/JSInterop/{src/Microsoft.JSInterop => Microsoft.JSInterop/src}/Json/SimpleJson/README.txt (100%) rename src/JSInterop/{src/Microsoft.JSInterop => Microsoft.JSInterop/src}/Json/SimpleJson/SimpleJson.cs (100%) create mode 100644 src/JSInterop/Microsoft.JSInterop/src/Microsoft.JSInterop.csproj rename src/JSInterop/{src/Microsoft.JSInterop => Microsoft.JSInterop/src}/Properties/AssemblyInfo.cs (100%) rename src/JSInterop/{src/Microsoft.JSInterop => Microsoft.JSInterop/src}/TaskGenericsUtil.cs (100%) rename src/JSInterop/{test/Microsoft.JSInterop.Test => Microsoft.JSInterop/test}/DotNetDispatcherTest.cs (100%) rename src/JSInterop/{test/Microsoft.JSInterop.Test => Microsoft.JSInterop/test}/DotNetObjectRefTest.cs (100%) rename src/JSInterop/{test/Microsoft.JSInterop.Test => Microsoft.JSInterop/test}/JSInProcessRuntimeBaseTest.cs (100%) rename src/JSInterop/{test/Microsoft.JSInterop.Test => Microsoft.JSInterop/test}/JSRuntimeBaseTest.cs (100%) rename src/JSInterop/{test/Microsoft.JSInterop.Test => Microsoft.JSInterop/test}/JSRuntimeTest.cs (100%) rename src/JSInterop/{test/Microsoft.JSInterop.Test => Microsoft.JSInterop/test}/JsonUtilTest.cs (100%) create mode 100644 src/JSInterop/Microsoft.JSInterop/test/Microsoft.JSInterop.Test.csproj rename src/JSInterop/{src/Mono.WebAssembly.Interop => Mono.WebAssembly.Interop/src}/InternalCalls.cs (100%) create mode 100644 src/JSInterop/Mono.WebAssembly.Interop/src/Mono.WebAssembly.Interop.csproj rename src/JSInterop/{src/Mono.WebAssembly.Interop => Mono.WebAssembly.Interop/src}/MonoWebAssemblyJSRuntime.cs (100%) delete mode 100644 src/JSInterop/src/Microsoft.JSInterop.JS/.gitignore delete mode 100644 src/JSInterop/src/Microsoft.JSInterop/Microsoft.JSInterop.csproj delete mode 100644 src/JSInterop/src/Mono.WebAssembly.Interop/Mono.WebAssembly.Interop.csproj delete mode 100644 src/JSInterop/test/Microsoft.JSInterop.Test/Microsoft.JSInterop.Test.csproj diff --git a/src/JSInterop/.editorconfig b/src/JSInterop/.editorconfig deleted file mode 100644 index 0d238f8e41..0000000000 --- a/src/JSInterop/.editorconfig +++ /dev/null @@ -1,27 +0,0 @@ -# All Files -[*] -charset = utf-8 -end_of_line = crlf -indent_style = space -indent_size = 4 -insert_final_newline = false -trim_trailing_whitespace = true - -# Solution Files -[*.sln] -indent_style = tab - -# Markdown Files -[*.md] -trim_trailing_whitespace = false - -# Web Files -[*.{htm,html,js,ts,css,scss,less}] -insert_final_newline = true -indent_size = 2 - -[*.{yml,json}] -indent_size = 2 - -[*.{xml,csproj,config,*proj,targets,props}] -indent_size = 2 diff --git a/src/JSInterop/Directory.Build.props b/src/JSInterop/Directory.Build.props new file mode 100644 index 0000000000..f25c1d90ce --- /dev/null +++ b/src/JSInterop/Directory.Build.props @@ -0,0 +1,7 @@ + + + + + true + + diff --git a/src/JSInterop/src/Microsoft.JSInterop.JS/Microsoft.JSInterop.JS.csproj b/src/JSInterop/Microsoft.JSInterop.JS/src/Microsoft.JSInterop.JS.csproj similarity index 76% rename from src/JSInterop/src/Microsoft.JSInterop.JS/Microsoft.JSInterop.JS.csproj rename to src/JSInterop/Microsoft.JSInterop.JS/src/Microsoft.JSInterop.JS.csproj index 4a6ba93ebb..7f806d0afa 100644 --- a/src/JSInterop/src/Microsoft.JSInterop.JS/Microsoft.JSInterop.JS.csproj +++ b/src/JSInterop/Microsoft.JSInterop.JS/src/Microsoft.JSInterop.JS.csproj @@ -7,7 +7,7 @@ - + diff --git a/src/JSInterop/src/Microsoft.JSInterop.JS/package-lock.json b/src/JSInterop/Microsoft.JSInterop.JS/src/package-lock.json similarity index 100% rename from src/JSInterop/src/Microsoft.JSInterop.JS/package-lock.json rename to src/JSInterop/Microsoft.JSInterop.JS/src/package-lock.json diff --git a/src/JSInterop/src/Microsoft.JSInterop.JS/package.json b/src/JSInterop/Microsoft.JSInterop.JS/src/package.json similarity index 100% rename from src/JSInterop/src/Microsoft.JSInterop.JS/package.json rename to src/JSInterop/Microsoft.JSInterop.JS/src/package.json diff --git a/src/JSInterop/src/Microsoft.JSInterop.JS/src/Microsoft.JSInterop.ts b/src/JSInterop/Microsoft.JSInterop.JS/src/src/Microsoft.JSInterop.ts similarity index 100% rename from src/JSInterop/src/Microsoft.JSInterop.JS/src/Microsoft.JSInterop.ts rename to src/JSInterop/Microsoft.JSInterop.JS/src/src/Microsoft.JSInterop.ts diff --git a/src/JSInterop/src/Microsoft.JSInterop.JS/tsconfig.json b/src/JSInterop/Microsoft.JSInterop.JS/src/tsconfig.json similarity index 100% rename from src/JSInterop/src/Microsoft.JSInterop.JS/tsconfig.json rename to src/JSInterop/Microsoft.JSInterop.JS/src/tsconfig.json diff --git a/src/JSInterop/Microsoft.JSInterop.sln b/src/JSInterop/Microsoft.JSInterop.sln deleted file mode 100644 index d646a6b068..0000000000 --- a/src/JSInterop/Microsoft.JSInterop.sln +++ /dev/null @@ -1,58 +0,0 @@ - -Microsoft Visual Studio Solution File, Format Version 12.00 -# Visual Studio 15 -VisualStudioVersion = 15.0.27703.2042 -MinimumVisualStudioVersion = 10.0.40219.1 -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{1290437E-A890-419E-A317-D0F7FEE185A5}" -EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "test", "test", "{B98D4F51-88FB-471C-B56F-752E8EE502E7}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.JSInterop", "src\Microsoft.JSInterop\Microsoft.JSInterop.csproj", "{CB4CD4A6-9BAA-46D1-944F-CE56DEC2663C}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.JSInterop.Test", "test\Microsoft.JSInterop.Test\Microsoft.JSInterop.Test.csproj", "{7FF8B199-52C0-4DFE-A73B-0C9E18220C0E}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.JSInterop.JS", "src\Microsoft.JSInterop.JS\Microsoft.JSInterop.JS.csproj", "{60BA5AAD-264A-437E-8319-577841C66CC6}" -EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{BE4CBB33-5C40-4A07-B6FC-1D7C3AE13024}" - ProjectSection(SolutionItems) = preProject - README.md = README.md - EndProjectSection -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Mono.WebAssembly.Interop", "src\Mono.WebAssembly.Interop\Mono.WebAssembly.Interop.csproj", "{10145E99-1B2D-40C5-9595-582BDAF3E024}" -EndProject -Global - GlobalSection(SolutionConfigurationPlatforms) = preSolution - Debug|Any CPU = Debug|Any CPU - Release|Any CPU = Release|Any CPU - EndGlobalSection - GlobalSection(ProjectConfigurationPlatforms) = postSolution - {CB4CD4A6-9BAA-46D1-944F-CE56DEC2663C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {CB4CD4A6-9BAA-46D1-944F-CE56DEC2663C}.Debug|Any CPU.Build.0 = Debug|Any CPU - {CB4CD4A6-9BAA-46D1-944F-CE56DEC2663C}.Release|Any CPU.ActiveCfg = Release|Any CPU - {CB4CD4A6-9BAA-46D1-944F-CE56DEC2663C}.Release|Any CPU.Build.0 = Release|Any CPU - {7FF8B199-52C0-4DFE-A73B-0C9E18220C0E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {7FF8B199-52C0-4DFE-A73B-0C9E18220C0E}.Debug|Any CPU.Build.0 = Debug|Any CPU - {7FF8B199-52C0-4DFE-A73B-0C9E18220C0E}.Release|Any CPU.ActiveCfg = Release|Any CPU - {7FF8B199-52C0-4DFE-A73B-0C9E18220C0E}.Release|Any CPU.Build.0 = Release|Any CPU - {60BA5AAD-264A-437E-8319-577841C66CC6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {60BA5AAD-264A-437E-8319-577841C66CC6}.Debug|Any CPU.Build.0 = Debug|Any CPU - {60BA5AAD-264A-437E-8319-577841C66CC6}.Release|Any CPU.ActiveCfg = Release|Any CPU - {60BA5AAD-264A-437E-8319-577841C66CC6}.Release|Any CPU.Build.0 = Release|Any CPU - {10145E99-1B2D-40C5-9595-582BDAF3E024}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {10145E99-1B2D-40C5-9595-582BDAF3E024}.Debug|Any CPU.Build.0 = Debug|Any CPU - {10145E99-1B2D-40C5-9595-582BDAF3E024}.Release|Any CPU.ActiveCfg = Release|Any CPU - {10145E99-1B2D-40C5-9595-582BDAF3E024}.Release|Any CPU.Build.0 = Release|Any CPU - EndGlobalSection - GlobalSection(SolutionProperties) = preSolution - HideSolutionNode = FALSE - EndGlobalSection - GlobalSection(NestedProjects) = preSolution - {CB4CD4A6-9BAA-46D1-944F-CE56DEC2663C} = {1290437E-A890-419E-A317-D0F7FEE185A5} - {7FF8B199-52C0-4DFE-A73B-0C9E18220C0E} = {B98D4F51-88FB-471C-B56F-752E8EE502E7} - {60BA5AAD-264A-437E-8319-577841C66CC6} = {1290437E-A890-419E-A317-D0F7FEE185A5} - {10145E99-1B2D-40C5-9595-582BDAF3E024} = {1290437E-A890-419E-A317-D0F7FEE185A5} - EndGlobalSection - GlobalSection(ExtensibilityGlobals) = postSolution - SolutionGuid = {7E07ABF2-427A-43FA-A6A4-82B21B96ACAF} - EndGlobalSection -EndGlobal diff --git a/src/JSInterop/src/Microsoft.JSInterop/DotNetDispatcher.cs b/src/JSInterop/Microsoft.JSInterop/src/DotNetDispatcher.cs similarity index 100% rename from src/JSInterop/src/Microsoft.JSInterop/DotNetDispatcher.cs rename to src/JSInterop/Microsoft.JSInterop/src/DotNetDispatcher.cs diff --git a/src/JSInterop/src/Microsoft.JSInterop/DotNetObjectRef.cs b/src/JSInterop/Microsoft.JSInterop/src/DotNetObjectRef.cs similarity index 100% rename from src/JSInterop/src/Microsoft.JSInterop/DotNetObjectRef.cs rename to src/JSInterop/Microsoft.JSInterop/src/DotNetObjectRef.cs diff --git a/src/JSInterop/src/Microsoft.JSInterop/ICustomArgSerializer.cs b/src/JSInterop/Microsoft.JSInterop/src/ICustomArgSerializer.cs similarity index 100% rename from src/JSInterop/src/Microsoft.JSInterop/ICustomArgSerializer.cs rename to src/JSInterop/Microsoft.JSInterop/src/ICustomArgSerializer.cs diff --git a/src/JSInterop/src/Microsoft.JSInterop/IJSInProcessRuntime.cs b/src/JSInterop/Microsoft.JSInterop/src/IJSInProcessRuntime.cs similarity index 100% rename from src/JSInterop/src/Microsoft.JSInterop/IJSInProcessRuntime.cs rename to src/JSInterop/Microsoft.JSInterop/src/IJSInProcessRuntime.cs diff --git a/src/JSInterop/src/Microsoft.JSInterop/IJSRuntime.cs b/src/JSInterop/Microsoft.JSInterop/src/IJSRuntime.cs similarity index 100% rename from src/JSInterop/src/Microsoft.JSInterop/IJSRuntime.cs rename to src/JSInterop/Microsoft.JSInterop/src/IJSRuntime.cs diff --git a/src/JSInterop/src/Microsoft.JSInterop/InteropArgSerializerStrategy.cs b/src/JSInterop/Microsoft.JSInterop/src/InteropArgSerializerStrategy.cs similarity index 100% rename from src/JSInterop/src/Microsoft.JSInterop/InteropArgSerializerStrategy.cs rename to src/JSInterop/Microsoft.JSInterop/src/InteropArgSerializerStrategy.cs diff --git a/src/JSInterop/src/Microsoft.JSInterop/JSAsyncCallResult.cs b/src/JSInterop/Microsoft.JSInterop/src/JSAsyncCallResult.cs similarity index 100% rename from src/JSInterop/src/Microsoft.JSInterop/JSAsyncCallResult.cs rename to src/JSInterop/Microsoft.JSInterop/src/JSAsyncCallResult.cs diff --git a/src/JSInterop/src/Microsoft.JSInterop/JSException.cs b/src/JSInterop/Microsoft.JSInterop/src/JSException.cs similarity index 100% rename from src/JSInterop/src/Microsoft.JSInterop/JSException.cs rename to src/JSInterop/Microsoft.JSInterop/src/JSException.cs diff --git a/src/JSInterop/src/Microsoft.JSInterop/JSInProcessRuntimeBase.cs b/src/JSInterop/Microsoft.JSInterop/src/JSInProcessRuntimeBase.cs similarity index 100% rename from src/JSInterop/src/Microsoft.JSInterop/JSInProcessRuntimeBase.cs rename to src/JSInterop/Microsoft.JSInterop/src/JSInProcessRuntimeBase.cs diff --git a/src/JSInterop/src/Microsoft.JSInterop/JSInvokableAttribute.cs b/src/JSInterop/Microsoft.JSInterop/src/JSInvokableAttribute.cs similarity index 100% rename from src/JSInterop/src/Microsoft.JSInterop/JSInvokableAttribute.cs rename to src/JSInterop/Microsoft.JSInterop/src/JSInvokableAttribute.cs diff --git a/src/JSInterop/src/Microsoft.JSInterop/JSRuntime.cs b/src/JSInterop/Microsoft.JSInterop/src/JSRuntime.cs similarity index 100% rename from src/JSInterop/src/Microsoft.JSInterop/JSRuntime.cs rename to src/JSInterop/Microsoft.JSInterop/src/JSRuntime.cs diff --git a/src/JSInterop/src/Microsoft.JSInterop/JSRuntimeBase.cs b/src/JSInterop/Microsoft.JSInterop/src/JSRuntimeBase.cs similarity index 100% rename from src/JSInterop/src/Microsoft.JSInterop/JSRuntimeBase.cs rename to src/JSInterop/Microsoft.JSInterop/src/JSRuntimeBase.cs diff --git a/src/JSInterop/src/Microsoft.JSInterop/Json/CamelCase.cs b/src/JSInterop/Microsoft.JSInterop/src/Json/CamelCase.cs similarity index 100% rename from src/JSInterop/src/Microsoft.JSInterop/Json/CamelCase.cs rename to src/JSInterop/Microsoft.JSInterop/src/Json/CamelCase.cs diff --git a/src/JSInterop/src/Microsoft.JSInterop/Json/Json.cs b/src/JSInterop/Microsoft.JSInterop/src/Json/Json.cs similarity index 100% rename from src/JSInterop/src/Microsoft.JSInterop/Json/Json.cs rename to src/JSInterop/Microsoft.JSInterop/src/Json/Json.cs diff --git a/src/JSInterop/src/Microsoft.JSInterop/Json/SimpleJson/README.txt b/src/JSInterop/Microsoft.JSInterop/src/Json/SimpleJson/README.txt similarity index 100% rename from src/JSInterop/src/Microsoft.JSInterop/Json/SimpleJson/README.txt rename to src/JSInterop/Microsoft.JSInterop/src/Json/SimpleJson/README.txt diff --git a/src/JSInterop/src/Microsoft.JSInterop/Json/SimpleJson/SimpleJson.cs b/src/JSInterop/Microsoft.JSInterop/src/Json/SimpleJson/SimpleJson.cs similarity index 100% rename from src/JSInterop/src/Microsoft.JSInterop/Json/SimpleJson/SimpleJson.cs rename to src/JSInterop/Microsoft.JSInterop/src/Json/SimpleJson/SimpleJson.cs diff --git a/src/JSInterop/Microsoft.JSInterop/src/Microsoft.JSInterop.csproj b/src/JSInterop/Microsoft.JSInterop/src/Microsoft.JSInterop.csproj new file mode 100644 index 0000000000..bdfea26ad5 --- /dev/null +++ b/src/JSInterop/Microsoft.JSInterop/src/Microsoft.JSInterop.csproj @@ -0,0 +1,10 @@ + + + + netstandard2.0 + Abstractions and features for interop between .NET and JavaScript code. + javascript;interop + true + + + diff --git a/src/JSInterop/src/Microsoft.JSInterop/Properties/AssemblyInfo.cs b/src/JSInterop/Microsoft.JSInterop/src/Properties/AssemblyInfo.cs similarity index 100% rename from src/JSInterop/src/Microsoft.JSInterop/Properties/AssemblyInfo.cs rename to src/JSInterop/Microsoft.JSInterop/src/Properties/AssemblyInfo.cs diff --git a/src/JSInterop/src/Microsoft.JSInterop/TaskGenericsUtil.cs b/src/JSInterop/Microsoft.JSInterop/src/TaskGenericsUtil.cs similarity index 100% rename from src/JSInterop/src/Microsoft.JSInterop/TaskGenericsUtil.cs rename to src/JSInterop/Microsoft.JSInterop/src/TaskGenericsUtil.cs diff --git a/src/JSInterop/test/Microsoft.JSInterop.Test/DotNetDispatcherTest.cs b/src/JSInterop/Microsoft.JSInterop/test/DotNetDispatcherTest.cs similarity index 100% rename from src/JSInterop/test/Microsoft.JSInterop.Test/DotNetDispatcherTest.cs rename to src/JSInterop/Microsoft.JSInterop/test/DotNetDispatcherTest.cs diff --git a/src/JSInterop/test/Microsoft.JSInterop.Test/DotNetObjectRefTest.cs b/src/JSInterop/Microsoft.JSInterop/test/DotNetObjectRefTest.cs similarity index 100% rename from src/JSInterop/test/Microsoft.JSInterop.Test/DotNetObjectRefTest.cs rename to src/JSInterop/Microsoft.JSInterop/test/DotNetObjectRefTest.cs diff --git a/src/JSInterop/test/Microsoft.JSInterop.Test/JSInProcessRuntimeBaseTest.cs b/src/JSInterop/Microsoft.JSInterop/test/JSInProcessRuntimeBaseTest.cs similarity index 100% rename from src/JSInterop/test/Microsoft.JSInterop.Test/JSInProcessRuntimeBaseTest.cs rename to src/JSInterop/Microsoft.JSInterop/test/JSInProcessRuntimeBaseTest.cs diff --git a/src/JSInterop/test/Microsoft.JSInterop.Test/JSRuntimeBaseTest.cs b/src/JSInterop/Microsoft.JSInterop/test/JSRuntimeBaseTest.cs similarity index 100% rename from src/JSInterop/test/Microsoft.JSInterop.Test/JSRuntimeBaseTest.cs rename to src/JSInterop/Microsoft.JSInterop/test/JSRuntimeBaseTest.cs diff --git a/src/JSInterop/test/Microsoft.JSInterop.Test/JSRuntimeTest.cs b/src/JSInterop/Microsoft.JSInterop/test/JSRuntimeTest.cs similarity index 100% rename from src/JSInterop/test/Microsoft.JSInterop.Test/JSRuntimeTest.cs rename to src/JSInterop/Microsoft.JSInterop/test/JSRuntimeTest.cs diff --git a/src/JSInterop/test/Microsoft.JSInterop.Test/JsonUtilTest.cs b/src/JSInterop/Microsoft.JSInterop/test/JsonUtilTest.cs similarity index 100% rename from src/JSInterop/test/Microsoft.JSInterop.Test/JsonUtilTest.cs rename to src/JSInterop/Microsoft.JSInterop/test/JsonUtilTest.cs diff --git a/src/JSInterop/Microsoft.JSInterop/test/Microsoft.JSInterop.Test.csproj b/src/JSInterop/Microsoft.JSInterop/test/Microsoft.JSInterop.Test.csproj new file mode 100644 index 0000000000..5e88f44a3a --- /dev/null +++ b/src/JSInterop/Microsoft.JSInterop/test/Microsoft.JSInterop.Test.csproj @@ -0,0 +1,11 @@ + + + + netcoreapp3.0 + + + + + + + diff --git a/src/JSInterop/src/Mono.WebAssembly.Interop/InternalCalls.cs b/src/JSInterop/Mono.WebAssembly.Interop/src/InternalCalls.cs similarity index 100% rename from src/JSInterop/src/Mono.WebAssembly.Interop/InternalCalls.cs rename to src/JSInterop/Mono.WebAssembly.Interop/src/InternalCalls.cs diff --git a/src/JSInterop/Mono.WebAssembly.Interop/src/Mono.WebAssembly.Interop.csproj b/src/JSInterop/Mono.WebAssembly.Interop/src/Mono.WebAssembly.Interop.csproj new file mode 100644 index 0000000000..75c8272e95 --- /dev/null +++ b/src/JSInterop/Mono.WebAssembly.Interop/src/Mono.WebAssembly.Interop.csproj @@ -0,0 +1,14 @@ + + + + netstandard2.0 + + false + false + + + + + + + diff --git a/src/JSInterop/src/Mono.WebAssembly.Interop/MonoWebAssemblyJSRuntime.cs b/src/JSInterop/Mono.WebAssembly.Interop/src/MonoWebAssemblyJSRuntime.cs similarity index 100% rename from src/JSInterop/src/Mono.WebAssembly.Interop/MonoWebAssemblyJSRuntime.cs rename to src/JSInterop/Mono.WebAssembly.Interop/src/MonoWebAssemblyJSRuntime.cs diff --git a/src/JSInterop/src/Microsoft.JSInterop.JS/.gitignore b/src/JSInterop/src/Microsoft.JSInterop.JS/.gitignore deleted file mode 100644 index 849ddff3b7..0000000000 --- a/src/JSInterop/src/Microsoft.JSInterop.JS/.gitignore +++ /dev/null @@ -1 +0,0 @@ -dist/ diff --git a/src/JSInterop/src/Microsoft.JSInterop/Microsoft.JSInterop.csproj b/src/JSInterop/src/Microsoft.JSInterop/Microsoft.JSInterop.csproj deleted file mode 100644 index 9f5c4f4abb..0000000000 --- a/src/JSInterop/src/Microsoft.JSInterop/Microsoft.JSInterop.csproj +++ /dev/null @@ -1,7 +0,0 @@ - - - - netstandard2.0 - - - diff --git a/src/JSInterop/src/Mono.WebAssembly.Interop/Mono.WebAssembly.Interop.csproj b/src/JSInterop/src/Mono.WebAssembly.Interop/Mono.WebAssembly.Interop.csproj deleted file mode 100644 index 81f1173b55..0000000000 --- a/src/JSInterop/src/Mono.WebAssembly.Interop/Mono.WebAssembly.Interop.csproj +++ /dev/null @@ -1,11 +0,0 @@ - - - - netstandard2.0 - - - - - - - diff --git a/src/JSInterop/test/Microsoft.JSInterop.Test/Microsoft.JSInterop.Test.csproj b/src/JSInterop/test/Microsoft.JSInterop.Test/Microsoft.JSInterop.Test.csproj deleted file mode 100644 index 893a11d33a..0000000000 --- a/src/JSInterop/test/Microsoft.JSInterop.Test/Microsoft.JSInterop.Test.csproj +++ /dev/null @@ -1,20 +0,0 @@ - - - - netcoreapp2.1 - false - 7.3 - - - - - - - - - - - - - - From 66773c57bcbcccdd63c993105b57de978eebeb39 Mon Sep 17 00:00:00 2001 From: Ryan Brandenburg Date: Tue, 29 Jan 2019 13:26:53 -0800 Subject: [PATCH 0048/1101] Use Arcade (dotnet/extensions#586) Use arcade \n\nCommit migrated from https://github.com/dotnet/extensions/commit/f0458995c625dba132a74733e041b97c0e10e025 --- .../Directory.Build.props | 2 +- ...Extensions.Configuration.KeyPerFile.csproj | 1 + src/FileProviders/Directory.Build.props | 2 +- ...t.Extensions.FileProviders.Embedded.csproj | 11 +++++---- .../Embedded/src/Properties/AssemblyInfo.cs | 4 ---- ...iders.Embedded.Manifest.Task.Tests.csproj} | 0 ...agnostics.HealthChecks.Abstractions.csproj | 1 + ...Extensions.Diagnostics.HealthChecks.csproj | 5 ++++ .../src/Properties/AssemblyInfo.cs | 3 --- .../src/Microsoft.JSInterop.csproj | 4 ++++ .../src/Properties/AssemblyInfo.cs | 3 --- .../test/DotNetDispatcherTest.cs | 17 +++++++------- .../test/DotNetObjectRefTest.cs | 2 +- .../test/JSInProcessRuntimeBaseTest.cs | 4 ++-- .../test/JSRuntimeBaseTest.cs | 4 ++-- .../Microsoft.JSInterop/test/JSRuntimeTest.cs | 2 +- .../Microsoft.JSInterop/test/JsonUtilTest.cs | 12 +++++----- .../test/Microsoft.JSInterop.Test.csproj | 11 --------- .../test/Microsoft.JSInterop.Tests.csproj | 15 ++++++++++++ .../test/xunit.runner.json | 3 +++ ...xtensions.Localization.Abstractions.csproj | 1 + .../Microsoft.Extensions.Localization.csproj | 5 ++++ .../src/Properties/AssemblyInfo.cs | 6 ----- .../Microsoft.Extensions.ObjectPool.csproj | 1 + .../ActivatorUtilities/sharedsources.props | 4 ++++ .../BenchmarkRunner/Directory.Build.props | 8 ------- ...Extensions.CommandLineUtils.Sources.shproj | 13 ----------- .../src/Microsoft.AspNetCore.Testing.csproj | 5 ++++ src/Testing/src/Properties/AssemblyInfo.cs | 6 ----- src/Testing/src/TestPathUtilities.cs | 7 +++++- .../src/xunit/ConditionalFactDiscoverer.cs | 4 ++-- .../src/xunit/ConditionalTheoryAttribute.cs | 2 +- .../src/xunit/ConditionalTheoryDiscoverer.cs | 23 +++++++++++++++++-- src/Testing/src/xunit/SkippedTestCase.cs | 14 +++++++---- .../Microsoft.AspNetCore.Testing.Tests.csproj | 1 - src/Testing/test/TestPathUtilitiesTest.cs | 2 +- .../Microsoft.Extensions.WebEncoders.csproj | 1 + 37 files changed, 117 insertions(+), 92 deletions(-) delete mode 100644 src/FileProviders/Embedded/src/Properties/AssemblyInfo.cs rename src/FileProviders/Manifest.MSBuildTask/test/{Microsoft.Extensions.FileProviders.Embedded.Manifest.Task.Test.csproj => Microsoft.Extensions.FileProviders.Embedded.Manifest.Task.Tests.csproj} (100%) delete mode 100644 src/HealthChecks/HealthChecks/src/Properties/AssemblyInfo.cs delete mode 100644 src/JSInterop/Microsoft.JSInterop/src/Properties/AssemblyInfo.cs delete mode 100644 src/JSInterop/Microsoft.JSInterop/test/Microsoft.JSInterop.Test.csproj create mode 100644 src/JSInterop/Microsoft.JSInterop/test/Microsoft.JSInterop.Tests.csproj create mode 100644 src/JSInterop/Microsoft.JSInterop/test/xunit.runner.json delete mode 100644 src/Localization/Localization/src/Properties/AssemblyInfo.cs delete mode 100644 src/Shared/BenchmarkRunner/Directory.Build.props delete mode 100644 src/Shared/CommandLineUtils/Microsoft.Extensions.CommandLineUtils.Sources.shproj delete mode 100644 src/Testing/src/Properties/AssemblyInfo.cs diff --git a/src/Configuration.KeyPerFile/Directory.Build.props b/src/Configuration.KeyPerFile/Directory.Build.props index 2082380096..fe35a9faec 100644 --- a/src/Configuration.KeyPerFile/Directory.Build.props +++ b/src/Configuration.KeyPerFile/Directory.Build.props @@ -2,7 +2,7 @@ - true + true configuration diff --git a/src/Configuration.KeyPerFile/src/Microsoft.Extensions.Configuration.KeyPerFile.csproj b/src/Configuration.KeyPerFile/src/Microsoft.Extensions.Configuration.KeyPerFile.csproj index 4eb19f3293..82784d0eed 100644 --- a/src/Configuration.KeyPerFile/src/Microsoft.Extensions.Configuration.KeyPerFile.csproj +++ b/src/Configuration.KeyPerFile/src/Microsoft.Extensions.Configuration.KeyPerFile.csproj @@ -4,6 +4,7 @@ Configuration provider that uses files in a directory for Microsoft.Extensions.Configuration. netstandard2.0 false + true diff --git a/src/FileProviders/Directory.Build.props b/src/FileProviders/Directory.Build.props index bf4410dcb7..709c47ddbd 100644 --- a/src/FileProviders/Directory.Build.props +++ b/src/FileProviders/Directory.Build.props @@ -2,7 +2,7 @@ - true + true files;filesystem diff --git a/src/FileProviders/Embedded/src/Microsoft.Extensions.FileProviders.Embedded.csproj b/src/FileProviders/Embedded/src/Microsoft.Extensions.FileProviders.Embedded.csproj index e8dd5d85dd..90cb2e4cc3 100644 --- a/src/FileProviders/Embedded/src/Microsoft.Extensions.FileProviders.Embedded.csproj +++ b/src/FileProviders/Embedded/src/Microsoft.Extensions.FileProviders.Embedded.csproj @@ -5,8 +5,13 @@ File provider for files in embedded resources for Microsoft.Extensions.FileProviders. netstandard2.0 $(MSBuildProjectName).nuspec + true + + + + @@ -18,7 +23,6 @@ - id=$(PackageId); @@ -40,10 +44,9 @@ OutputDocumentation=@(DocumentationProjectOutputGroupOutput); - TaskAssemblyNetStandard=..\..\Manifest.MSBuildTask\src\bin\$(Configuration)\netstandard2.0\$(AssemblyName).Manifest.Task.dll; - TaskSymbolNetStandard=..\..\Manifest.MSBuildTask\src\bin\$(Configuration)\netstandard2.0\$(AssemblyName).Manifest.Task.pdb; + TaskAssemblyNetStandard=$(ArtifactsDir)bin\$(AssemblyName).Manifest.Task\$(Configuration)\netstandard2.0\$(AssemblyName).Manifest.Task.dll; + TaskSymbolNetStandard=$(ArtifactsDir)bin\$(AssemblyName).Manifest.Task\$(Configuration)\netstandard2.0\$(AssemblyName).Manifest.Task.pdb; - diff --git a/src/FileProviders/Embedded/src/Properties/AssemblyInfo.cs b/src/FileProviders/Embedded/src/Properties/AssemblyInfo.cs deleted file mode 100644 index 610a7fa706..0000000000 --- a/src/FileProviders/Embedded/src/Properties/AssemblyInfo.cs +++ /dev/null @@ -1,4 +0,0 @@ - -using System.Runtime.CompilerServices; - -[assembly: InternalsVisibleTo("Microsoft.Extensions.FileProviders.Embedded.Tests, PublicKey=0024000004800000940000000602000000240000525341310004000001000100f33a29044fa9d740c9b3213a93e57c84b472c84e0b8a0e1ae48e67a9f8f6de9d5f7f3d52ac23e48ac51801f1dc950abe901da34d2a9e3baadb141a17c77ef3c565dd5ee5054b91cf63bb3c6ab83f72ab3aafe93d0fc3c2348b764fafb0b1c0733de51459aeab46580384bf9d74c4e28164b7cde247f891ba07891c9d872ad2bb")] \ No newline at end of file diff --git a/src/FileProviders/Manifest.MSBuildTask/test/Microsoft.Extensions.FileProviders.Embedded.Manifest.Task.Test.csproj b/src/FileProviders/Manifest.MSBuildTask/test/Microsoft.Extensions.FileProviders.Embedded.Manifest.Task.Tests.csproj similarity index 100% rename from src/FileProviders/Manifest.MSBuildTask/test/Microsoft.Extensions.FileProviders.Embedded.Manifest.Task.Test.csproj rename to src/FileProviders/Manifest.MSBuildTask/test/Microsoft.Extensions.FileProviders.Embedded.Manifest.Task.Tests.csproj diff --git a/src/HealthChecks/Abstractions/src/Microsoft.Extensions.Diagnostics.HealthChecks.Abstractions.csproj b/src/HealthChecks/Abstractions/src/Microsoft.Extensions.Diagnostics.HealthChecks.Abstractions.csproj index b95d66f7b3..69298be027 100644 --- a/src/HealthChecks/Abstractions/src/Microsoft.Extensions.Diagnostics.HealthChecks.Abstractions.csproj +++ b/src/HealthChecks/Abstractions/src/Microsoft.Extensions.Diagnostics.HealthChecks.Abstractions.csproj @@ -11,6 +11,7 @@ Microsoft.Extensions.Diagnostics.HealthChecks.IHealthCheck $(NoWarn);CS1591 true diagnostics;healthchecks + true diff --git a/src/HealthChecks/HealthChecks/src/Microsoft.Extensions.Diagnostics.HealthChecks.csproj b/src/HealthChecks/HealthChecks/src/Microsoft.Extensions.Diagnostics.HealthChecks.csproj index d0b1c97ef0..b49f194d0c 100644 --- a/src/HealthChecks/HealthChecks/src/Microsoft.Extensions.Diagnostics.HealthChecks.csproj +++ b/src/HealthChecks/HealthChecks/src/Microsoft.Extensions.Diagnostics.HealthChecks.csproj @@ -10,8 +10,13 @@ Microsoft.Extensions.Diagnostics.HealthChecks.IHealthChecksBuilder $(NoWarn);CS1591 true diagnostics;healthchecks + true + + + + diff --git a/src/HealthChecks/HealthChecks/src/Properties/AssemblyInfo.cs b/src/HealthChecks/HealthChecks/src/Properties/AssemblyInfo.cs deleted file mode 100644 index 13e969bfad..0000000000 --- a/src/HealthChecks/HealthChecks/src/Properties/AssemblyInfo.cs +++ /dev/null @@ -1,3 +0,0 @@ -using System.Runtime.CompilerServices; - -[assembly: InternalsVisibleTo("Microsoft.Extensions.Diagnostics.HealthChecks.Tests, PublicKey=0024000004800000940000000602000000240000525341310004000001000100f33a29044fa9d740c9b3213a93e57c84b472c84e0b8a0e1ae48e67a9f8f6de9d5f7f3d52ac23e48ac51801f1dc950abe901da34d2a9e3baadb141a17c77ef3c565dd5ee5054b91cf63bb3c6ab83f72ab3aafe93d0fc3c2348b764fafb0b1c0733de51459aeab46580384bf9d74c4e28164b7cde247f891ba07891c9d872ad2bb")] \ No newline at end of file diff --git a/src/JSInterop/Microsoft.JSInterop/src/Microsoft.JSInterop.csproj b/src/JSInterop/Microsoft.JSInterop/src/Microsoft.JSInterop.csproj index bdfea26ad5..d91e8c2524 100644 --- a/src/JSInterop/Microsoft.JSInterop/src/Microsoft.JSInterop.csproj +++ b/src/JSInterop/Microsoft.JSInterop/src/Microsoft.JSInterop.csproj @@ -7,4 +7,8 @@ true + + + + diff --git a/src/JSInterop/Microsoft.JSInterop/src/Properties/AssemblyInfo.cs b/src/JSInterop/Microsoft.JSInterop/src/Properties/AssemblyInfo.cs deleted file mode 100644 index d65c89dc7f..0000000000 --- a/src/JSInterop/Microsoft.JSInterop/src/Properties/AssemblyInfo.cs +++ /dev/null @@ -1,3 +0,0 @@ -using System.Runtime.CompilerServices; - -[assembly: InternalsVisibleTo("Microsoft.JSInterop.Test, PublicKey=0024000004800000940000000602000000240000525341310004000001000100f33a29044fa9d740c9b3213a93e57c84b472c84e0b8a0e1ae48e67a9f8f6de9d5f7f3d52ac23e48ac51801f1dc950abe901da34d2a9e3baadb141a17c77ef3c565dd5ee5054b91cf63bb3c6ab83f72ab3aafe93d0fc3c2348b764fafb0b1c0733de51459aeab46580384bf9d74c4e28164b7cde247f891ba07891c9d872ad2bb")] diff --git a/src/JSInterop/Microsoft.JSInterop/test/DotNetDispatcherTest.cs b/src/JSInterop/Microsoft.JSInterop/test/DotNetDispatcherTest.cs index eec537f987..93ef9a2498 100644 --- a/src/JSInterop/Microsoft.JSInterop/test/DotNetDispatcherTest.cs +++ b/src/JSInterop/Microsoft.JSInterop/test/DotNetDispatcherTest.cs @@ -6,7 +6,7 @@ using System.Linq; using System.Threading.Tasks; using Xunit; -namespace Microsoft.JSInterop.Test +namespace Microsoft.JSInterop.Tests { public class DotNetDispatcherTest { @@ -73,7 +73,7 @@ namespace Microsoft.JSInterop.Test Assert.Equal($"The assembly '{thisAssemblyName}' does not contain a public method with [JSInvokableAttribute(\"{methodIdentifier}\")].", ex.Message); } - [Fact] + [Fact(Skip = "https://github.com/aspnet/AspNetCore-Internal/issues/1733")] public Task CanInvokeStaticVoidMethod() => WithJSRuntime(jsRuntime => { // Arrange/Act @@ -109,7 +109,7 @@ namespace Microsoft.JSInterop.Test Assert.Equal(456, result.IntVal); }); - [Fact] + [Fact(Skip = "https://github.com/aspnet/AspNetCore-Internal/issues/1733")] public Task CanInvokeStaticWithParams() => WithJSRuntime(jsRuntime => { // Arrange: Track a .NET object to use as an arg @@ -140,7 +140,7 @@ namespace Microsoft.JSInterop.Test Assert.Equal(1299, resultDto2.IntVal); }); - [Fact] + [Fact(Skip = "https://github.com/aspnet/AspNetCore-Internal/issues/1733")] public Task CanInvokeInstanceVoidMethod() => WithJSRuntime(jsRuntime => { // Arrange: Track some instance @@ -155,7 +155,7 @@ namespace Microsoft.JSInterop.Test Assert.True(targetInstance.DidInvokeMyInvocableInstanceVoid); }); - [Fact] + [Fact(Skip = "https://github.com/aspnet/AspNetCore-Internal/issues/1733")] public Task CanInvokeBaseInstanceVoidMethod() => WithJSRuntime(jsRuntime => { // Arrange: Track some instance @@ -206,7 +206,7 @@ namespace Microsoft.JSInterop.Test Assert.StartsWith("There is no tracked object with id '1'.", ex.Message); }); - [Fact] + [Fact(Skip = "https://github.com/aspnet/AspNetCore-Internal/issues/1733")] public Task CanInvokeInstanceMethodWithParams() => WithJSRuntime(jsRuntime => { // Arrange: Track some instance plus another object we'll pass as a param @@ -242,7 +242,7 @@ namespace Microsoft.JSInterop.Test Assert.Equal("In call to 'InvocableStaticWithParams', expected 3 parameters but received 4.", ex.Message); } - [Fact] + [Fact(Skip = "https://github.com/aspnet/AspNetCore-Internal/issues/1733")] public Task CanInvokeAsyncMethod() => WithJSRuntime(async jsRuntime => { // Arrange: Track some instance plus another object we'll pass as a param @@ -320,7 +320,8 @@ namespace Microsoft.JSInterop.Test protected static void StaticMethodWithoutAttribute() { } protected static void InstanceMethodWithoutAttribute() { } - [JSInvokable("InvocableStaticVoid")] public static void MyInvocableVoid() + [JSInvokable("InvocableStaticVoid")] + public static void MyInvocableVoid() { DidInvokeMyInvocableStaticVoid = true; } diff --git a/src/JSInterop/Microsoft.JSInterop/test/DotNetObjectRefTest.cs b/src/JSInterop/Microsoft.JSInterop/test/DotNetObjectRefTest.cs index 969dcae79d..1bdec6d465 100644 --- a/src/JSInterop/Microsoft.JSInterop/test/DotNetObjectRefTest.cs +++ b/src/JSInterop/Microsoft.JSInterop/test/DotNetObjectRefTest.cs @@ -6,7 +6,7 @@ using System.Collections.Generic; using System.Threading.Tasks; using Xunit; -namespace Microsoft.JSInterop.Test +namespace Microsoft.JSInterop.Tests { public class DotNetObjectRefTest { diff --git a/src/JSInterop/Microsoft.JSInterop/test/JSInProcessRuntimeBaseTest.cs b/src/JSInterop/Microsoft.JSInterop/test/JSInProcessRuntimeBaseTest.cs index d2e71f6eb2..36474fe407 100644 --- a/src/JSInterop/Microsoft.JSInterop/test/JSInProcessRuntimeBaseTest.cs +++ b/src/JSInterop/Microsoft.JSInterop/test/JSInProcessRuntimeBaseTest.cs @@ -6,7 +6,7 @@ using System.Collections.Generic; using System.Linq; using Xunit; -namespace Microsoft.JSInterop.Test +namespace Microsoft.JSInterop.Tests { public class JSInProcessRuntimeBaseTest { @@ -21,7 +21,7 @@ namespace Microsoft.JSInterop.Test }; // Act - var syncResult = runtime.Invoke("test identifier 1", "arg1", 123, true ); + var syncResult = runtime.Invoke("test identifier 1", "arg1", 123, true); var call = runtime.InvokeCalls.Single(); // Assert diff --git a/src/JSInterop/Microsoft.JSInterop/test/JSRuntimeBaseTest.cs b/src/JSInterop/Microsoft.JSInterop/test/JSRuntimeBaseTest.cs index 9193d6deb8..ab048e812f 100644 --- a/src/JSInterop/Microsoft.JSInterop/test/JSRuntimeBaseTest.cs +++ b/src/JSInterop/Microsoft.JSInterop/test/JSRuntimeBaseTest.cs @@ -7,7 +7,7 @@ using System.Collections.Generic; using System.Linq; using Xunit; -namespace Microsoft.JSInterop.Test +namespace Microsoft.JSInterop.Tests { public class JSRuntimeBaseTest { @@ -18,7 +18,7 @@ namespace Microsoft.JSInterop.Test var runtime = new TestJSRuntime(); // Act - runtime.InvokeAsync("test identifier 1", "arg1", 123, true ); + runtime.InvokeAsync("test identifier 1", "arg1", 123, true); runtime.InvokeAsync("test identifier 2", "some other arg"); // Assert diff --git a/src/JSInterop/Microsoft.JSInterop/test/JSRuntimeTest.cs b/src/JSInterop/Microsoft.JSInterop/test/JSRuntimeTest.cs index d5fed45ea4..b8a1c363dc 100644 --- a/src/JSInterop/Microsoft.JSInterop/test/JSRuntimeTest.cs +++ b/src/JSInterop/Microsoft.JSInterop/test/JSRuntimeTest.cs @@ -6,7 +6,7 @@ using System.Linq; using System.Threading.Tasks; using Xunit; -namespace Microsoft.JSInterop.Test +namespace Microsoft.JSInterop.Tests { public class JSRuntimeTest { diff --git a/src/JSInterop/Microsoft.JSInterop/test/JsonUtilTest.cs b/src/JSInterop/Microsoft.JSInterop/test/JsonUtilTest.cs index 1be98b681e..2b239faab9 100644 --- a/src/JSInterop/Microsoft.JSInterop/test/JsonUtilTest.cs +++ b/src/JSInterop/Microsoft.JSInterop/test/JsonUtilTest.cs @@ -6,7 +6,7 @@ using System; using System.Collections.Generic; using Xunit; -namespace Microsoft.JSInterop.Test +namespace Microsoft.JSInterop.Tests { public class JsonUtilTest { @@ -124,10 +124,10 @@ namespace Microsoft.JSInterop.Test BoolProperty = true, NullableIntProperty = 1 }; - + // Act var result = Json.Serialize(commandResult); - + // Assert Assert.Equal("{\"stringProperty\":\"Test\",\"boolProperty\":true,\"nullableIntProperty\":1}", result); } @@ -222,12 +222,12 @@ namespace Microsoft.JSInterop.Test // Act var exception = Assert.Throws(() => { - Json.Deserialize(json); + Json.Deserialize(json); }); // Assert Assert.Equal( - $"Cannot deserialize JSON into type '{type.FullName}' because it does not have a public parameterless constructor.", + $"Cannot deserialize JSON into type '{type.FullName}' because it does not have a public parameterless constructor.", exception.Message); } @@ -277,7 +277,7 @@ namespace Microsoft.JSInterop.Test class NonEmptyConstructorPoco { - public NonEmptyConstructorPoco(int parameter) {} + public NonEmptyConstructorPoco(int parameter) { } public int Property { get; set; } } diff --git a/src/JSInterop/Microsoft.JSInterop/test/Microsoft.JSInterop.Test.csproj b/src/JSInterop/Microsoft.JSInterop/test/Microsoft.JSInterop.Test.csproj deleted file mode 100644 index 5e88f44a3a..0000000000 --- a/src/JSInterop/Microsoft.JSInterop/test/Microsoft.JSInterop.Test.csproj +++ /dev/null @@ -1,11 +0,0 @@ - - - - netcoreapp3.0 - - - - - - - diff --git a/src/JSInterop/Microsoft.JSInterop/test/Microsoft.JSInterop.Tests.csproj b/src/JSInterop/Microsoft.JSInterop/test/Microsoft.JSInterop.Tests.csproj new file mode 100644 index 0000000000..a39b082180 --- /dev/null +++ b/src/JSInterop/Microsoft.JSInterop/test/Microsoft.JSInterop.Tests.csproj @@ -0,0 +1,15 @@ + + + + netcoreapp3.0 + + + + + + + + + + + diff --git a/src/JSInterop/Microsoft.JSInterop/test/xunit.runner.json b/src/JSInterop/Microsoft.JSInterop/test/xunit.runner.json new file mode 100644 index 0000000000..0f2ad9f769 --- /dev/null +++ b/src/JSInterop/Microsoft.JSInterop/test/xunit.runner.json @@ -0,0 +1,3 @@ +{ + "shadowCopy": true +} diff --git a/src/Localization/Abstractions/src/Microsoft.Extensions.Localization.Abstractions.csproj b/src/Localization/Abstractions/src/Microsoft.Extensions.Localization.Abstractions.csproj index 8508eb071a..636072724f 100644 --- a/src/Localization/Abstractions/src/Microsoft.Extensions.Localization.Abstractions.csproj +++ b/src/Localization/Abstractions/src/Microsoft.Extensions.Localization.Abstractions.csproj @@ -10,6 +10,7 @@ Microsoft.Extensions.Localization.IStringLocalizer<T> $(NoWarn);CS1591 true localization + true diff --git a/src/Localization/Localization/src/Microsoft.Extensions.Localization.csproj b/src/Localization/Localization/src/Microsoft.Extensions.Localization.csproj index 73365a15eb..8a820ac7b9 100644 --- a/src/Localization/Localization/src/Microsoft.Extensions.Localization.csproj +++ b/src/Localization/Localization/src/Microsoft.Extensions.Localization.csproj @@ -7,6 +7,7 @@ $(NoWarn);CS1591 true localization + true @@ -16,4 +17,8 @@ + + + + diff --git a/src/Localization/Localization/src/Properties/AssemblyInfo.cs b/src/Localization/Localization/src/Properties/AssemblyInfo.cs deleted file mode 100644 index 3e297b801e..0000000000 --- a/src/Localization/Localization/src/Properties/AssemblyInfo.cs +++ /dev/null @@ -1,6 +0,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.Runtime.CompilerServices; - -[assembly: InternalsVisibleTo("Microsoft.Extensions.Localization.Tests, PublicKey=0024000004800000940000000602000000240000525341310004000001000100f33a29044fa9d740c9b3213a93e57c84b472c84e0b8a0e1ae48e67a9f8f6de9d5f7f3d52ac23e48ac51801f1dc950abe901da34d2a9e3baadb141a17c77ef3c565dd5ee5054b91cf63bb3c6ab83f72ab3aafe93d0fc3c2348b764fafb0b1c0733de51459aeab46580384bf9d74c4e28164b7cde247f891ba07891c9d872ad2bb")] diff --git a/src/ObjectPool/src/Microsoft.Extensions.ObjectPool.csproj b/src/ObjectPool/src/Microsoft.Extensions.ObjectPool.csproj index cb42c5615a..646d18a8b7 100644 --- a/src/ObjectPool/src/Microsoft.Extensions.ObjectPool.csproj +++ b/src/ObjectPool/src/Microsoft.Extensions.ObjectPool.csproj @@ -6,6 +6,7 @@ $(NoWarn);CS1591 true pooling + true diff --git a/src/Shared/ActivatorUtilities/sharedsources.props b/src/Shared/ActivatorUtilities/sharedsources.props index b35fe34b10..f754677531 100644 --- a/src/Shared/ActivatorUtilities/sharedsources.props +++ b/src/Shared/ActivatorUtilities/sharedsources.props @@ -1,4 +1,8 @@ + + false + + true diff --git a/src/Shared/BenchmarkRunner/Directory.Build.props b/src/Shared/BenchmarkRunner/Directory.Build.props deleted file mode 100644 index d2f65e8d3d..0000000000 --- a/src/Shared/BenchmarkRunner/Directory.Build.props +++ /dev/null @@ -1,8 +0,0 @@ - - - - - - Microsoft.AspNetCore.BenchmarkRunner.Sources - - diff --git a/src/Shared/CommandLineUtils/Microsoft.Extensions.CommandLineUtils.Sources.shproj b/src/Shared/CommandLineUtils/Microsoft.Extensions.CommandLineUtils.Sources.shproj deleted file mode 100644 index c728fe1012..0000000000 --- a/src/Shared/CommandLineUtils/Microsoft.Extensions.CommandLineUtils.Sources.shproj +++ /dev/null @@ -1,13 +0,0 @@ - - - - 00947d4a-c20e-46e3-90c3-6cd6bc87ee72 - 14.0 - - - - - - - - diff --git a/src/Testing/src/Microsoft.AspNetCore.Testing.csproj b/src/Testing/src/Microsoft.AspNetCore.Testing.csproj index 64e0b3c4e1..0e33852b62 100644 --- a/src/Testing/src/Microsoft.AspNetCore.Testing.csproj +++ b/src/Testing/src/Microsoft.AspNetCore.Testing.csproj @@ -8,10 +8,15 @@ aspnetcore false true + false true + + + + diff --git a/src/Testing/src/Properties/AssemblyInfo.cs b/src/Testing/src/Properties/AssemblyInfo.cs deleted file mode 100644 index 0212e111ee..0000000000 --- a/src/Testing/src/Properties/AssemblyInfo.cs +++ /dev/null @@ -1,6 +0,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.Runtime.CompilerServices; - -[assembly: InternalsVisibleTo("Microsoft.AspNetCore.Testing.Tests, PublicKey=0024000004800000940000000602000000240000525341310004000001000100f33a29044fa9d740c9b3213a93e57c84b472c84e0b8a0e1ae48e67a9f8f6de9d5f7f3d52ac23e48ac51801f1dc950abe901da34d2a9e3baadb141a17c77ef3c565dd5ee5054b91cf63bb3c6ab83f72ab3aafe93d0fc3c2348b764fafb0b1c0733de51459aeab46580384bf9d74c4e28164b7cde247f891ba07891c9d872ad2bb")] diff --git a/src/Testing/src/TestPathUtilities.cs b/src/Testing/src/TestPathUtilities.cs index ebd10897c3..f982471f39 100644 --- a/src/Testing/src/TestPathUtilities.cs +++ b/src/Testing/src/TestPathUtilities.cs @@ -1,4 +1,4 @@ -// Copyright (c) .NET Foundation. All rights reserved. +// 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; @@ -8,6 +8,11 @@ namespace Microsoft.AspNetCore.Testing { public class TestPathUtilities { + public static string GetRepoRootDirectory() + { + return GetSolutionRootDirectory("Extensions"); + } + public static string GetSolutionRootDirectory(string solution) { var applicationBasePath = AppContext.BaseDirectory; diff --git a/src/Testing/src/xunit/ConditionalFactDiscoverer.cs b/src/Testing/src/xunit/ConditionalFactDiscoverer.cs index 819373fa31..cf49b29e5a 100644 --- a/src/Testing/src/xunit/ConditionalFactDiscoverer.cs +++ b/src/Testing/src/xunit/ConditionalFactDiscoverer.cs @@ -20,8 +20,8 @@ namespace Microsoft.AspNetCore.Testing.xunit { var skipReason = testMethod.EvaluateSkipConditions(); return skipReason != null - ? new SkippedTestCase(skipReason, _diagnosticMessageSink, discoveryOptions.MethodDisplayOrDefault(), testMethod) + ? new SkippedTestCase(skipReason, _diagnosticMessageSink, discoveryOptions.MethodDisplayOrDefault(), TestMethodDisplayOptions.None, testMethod) : base.CreateTestCase(discoveryOptions, testMethod, factAttribute); } } -} \ No newline at end of file +} diff --git a/src/Testing/src/xunit/ConditionalTheoryAttribute.cs b/src/Testing/src/xunit/ConditionalTheoryAttribute.cs index 9249078cc5..fe45f2ffc6 100644 --- a/src/Testing/src/xunit/ConditionalTheoryAttribute.cs +++ b/src/Testing/src/xunit/ConditionalTheoryAttribute.cs @@ -12,4 +12,4 @@ namespace Microsoft.AspNetCore.Testing.xunit public class ConditionalTheoryAttribute : TheoryAttribute { } -} \ No newline at end of file +} diff --git a/src/Testing/src/xunit/ConditionalTheoryDiscoverer.cs b/src/Testing/src/xunit/ConditionalTheoryDiscoverer.cs index d24421f5cd..9e413cd580 100644 --- a/src/Testing/src/xunit/ConditionalTheoryDiscoverer.cs +++ b/src/Testing/src/xunit/ConditionalTheoryDiscoverer.cs @@ -14,11 +14,30 @@ namespace Microsoft.AspNetCore.Testing.xunit { } + private sealed class OptionsWithPreEnumerationEnabled : ITestFrameworkDiscoveryOptions + { + private const string PreEnumerateTheories = "xunit.discovery.PreEnumerateTheories"; + + private readonly ITestFrameworkDiscoveryOptions _original; + + public OptionsWithPreEnumerationEnabled(ITestFrameworkDiscoveryOptions original) + => _original = original; + + public TValue GetValue(string name) + => (name == PreEnumerateTheories) ? (TValue)(object)true : _original.GetValue(name); + + public void SetValue(string name, TValue value) + => _original.SetValue(name, value); + } + + public override IEnumerable Discover(ITestFrameworkDiscoveryOptions discoveryOptions, ITestMethod testMethod, IAttributeInfo theoryAttribute) + => base.Discover(new OptionsWithPreEnumerationEnabled(discoveryOptions), testMethod, theoryAttribute); + protected override IEnumerable CreateTestCasesForTheory(ITestFrameworkDiscoveryOptions discoveryOptions, ITestMethod testMethod, IAttributeInfo theoryAttribute) { var skipReason = testMethod.EvaluateSkipConditions(); return skipReason != null - ? new[] { new SkippedTestCase(skipReason, DiagnosticMessageSink, discoveryOptions.MethodDisplayOrDefault(), testMethod) } + ? new[] { new SkippedTestCase(skipReason, DiagnosticMessageSink, discoveryOptions.MethodDisplayOrDefault(), TestMethodDisplayOptions.None, testMethod) } : base.CreateTestCasesForTheory(discoveryOptions, testMethod, theoryAttribute); } @@ -44,4 +63,4 @@ namespace Microsoft.AspNetCore.Testing.xunit : base.CreateTestCasesForDataRow(discoveryOptions, testMethod, theoryAttribute, dataRow); } } -} \ No newline at end of file +} diff --git a/src/Testing/src/xunit/SkippedTestCase.cs b/src/Testing/src/xunit/SkippedTestCase.cs index c2e15fa640..1c25c507b9 100644 --- a/src/Testing/src/xunit/SkippedTestCase.cs +++ b/src/Testing/src/xunit/SkippedTestCase.cs @@ -1,4 +1,4 @@ -// Copyright (c) .NET Foundation. All rights reserved. +// 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; @@ -16,8 +16,14 @@ namespace Microsoft.AspNetCore.Testing.xunit { } - public SkippedTestCase(string skipReason, IMessageSink diagnosticMessageSink, TestMethodDisplay defaultMethodDisplay, ITestMethod testMethod, object[] testMethodArguments = null) - : base(diagnosticMessageSink, defaultMethodDisplay, testMethod, testMethodArguments) + public SkippedTestCase( + string skipReason, + IMessageSink diagnosticMessageSink, + TestMethodDisplay defaultMethodDisplay, + TestMethodDisplayOptions defaultMethodDisplayOptions, + ITestMethod testMethod, + object[] testMethodArguments = null) + : base(diagnosticMessageSink, defaultMethodDisplay, defaultMethodDisplayOptions, testMethod, testMethodArguments) { _skipReason = skipReason; } @@ -37,4 +43,4 @@ namespace Microsoft.AspNetCore.Testing.xunit data.AddValue(nameof(_skipReason), _skipReason); } } -} \ No newline at end of file +} diff --git a/src/Testing/test/Microsoft.AspNetCore.Testing.Tests.csproj b/src/Testing/test/Microsoft.AspNetCore.Testing.Tests.csproj index 691c9ccd76..acfb34b320 100644 --- a/src/Testing/test/Microsoft.AspNetCore.Testing.Tests.csproj +++ b/src/Testing/test/Microsoft.AspNetCore.Testing.Tests.csproj @@ -24,5 +24,4 @@ - diff --git a/src/Testing/test/TestPathUtilitiesTest.cs b/src/Testing/test/TestPathUtilitiesTest.cs index 0c9a7c5ee4..c77194a548 100644 --- a/src/Testing/test/TestPathUtilitiesTest.cs +++ b/src/Testing/test/TestPathUtilitiesTest.cs @@ -16,7 +16,7 @@ namespace Microsoft.AspNetCore.Testing // Testing\test\Microsoft.AspNetCore.Testing.Tests\bin\Debug\netcoreapp2.0 // Testing\test\Microsoft.AspNetCore.Testing.Tests\bin\Debug\net461 // Testing\test\Microsoft.AspNetCore.Testing.Tests\bin\Debug\net46 - var expectedPath = Path.GetFullPath(Path.Combine(Directory.GetCurrentDirectory(), "..", "..", "..", "..", "..", "..", "..")); + var expectedPath = Path.GetFullPath(Path.Combine(Directory.GetCurrentDirectory(), "..", "..", "..", "..", "..")); Assert.Equal(expectedPath, TestPathUtilities.GetSolutionRootDirectory("Extensions")); } diff --git a/src/WebEncoders/src/Microsoft.Extensions.WebEncoders.csproj b/src/WebEncoders/src/Microsoft.Extensions.WebEncoders.csproj index 18f96d9412..bc73592b3f 100644 --- a/src/WebEncoders/src/Microsoft.Extensions.WebEncoders.csproj +++ b/src/WebEncoders/src/Microsoft.Extensions.WebEncoders.csproj @@ -7,6 +7,7 @@ true true aspnetcore + true From fb64cd68876a1218faf6f13639a4d7610a7a90f3 Mon Sep 17 00:00:00 2001 From: Ryan Brandenburg Date: Tue, 29 Jan 2019 17:05:25 -0800 Subject: [PATCH 0049/1101] Reduce build times (dotnet/extensions#1016) * Fix package artifacts taking too long to upload * Make sure new packages are packaged \n\nCommit migrated from https://github.com/dotnet/extensions/commit/12613bac075ddd60e2dc8d3eea6510df55ee4e7c --- src/JSInterop/Microsoft.JSInterop/src/Microsoft.JSInterop.csproj | 1 + 1 file changed, 1 insertion(+) diff --git a/src/JSInterop/Microsoft.JSInterop/src/Microsoft.JSInterop.csproj b/src/JSInterop/Microsoft.JSInterop/src/Microsoft.JSInterop.csproj index d91e8c2524..096b5d5bdd 100644 --- a/src/JSInterop/Microsoft.JSInterop/src/Microsoft.JSInterop.csproj +++ b/src/JSInterop/Microsoft.JSInterop/src/Microsoft.JSInterop.csproj @@ -5,6 +5,7 @@ Abstractions and features for interop between .NET and JavaScript code. javascript;interop true + true From 154ab28e451898d023e9c3cca97c2c6fb3590a1b Mon Sep 17 00:00:00 2001 From: Nate McMaster Date: Tue, 29 Jan 2019 18:34:54 -0800 Subject: [PATCH 0050/1101] Cleanup conversion to Arcade (dotnet/extensions#1014) * Remove obsolete targets, properties, and scripts * Replace IsProductComponent with IsShipping * Undo bad merge to version.props * Update documentation, and put workarounds into a common file * Replace usages of RepositoryRoot with RepoRoot * Remove API baselines * Remove unnecessary restore feeds and split workarounds into two files * Enable PR checks on all branches, and disable autocancel\n\nCommit migrated from https://github.com/dotnet/extensions/commit/f41cfded3c12eec0efea89ece1dafe43afa9c6b8 --- ...Extensions.Configuration.KeyPerFile.csproj | 3 +- .../Embedded/Directory.Build.props | 7 - ...t.Extensions.FileProviders.Embedded.csproj | 2 +- .../Embedded/src/baseline.netcore.json | 343 --------- ...ileProviders.Embedded.Manifest.Task.csproj | 2 +- .../Abstractions/Directory.Build.props | 7 - ...agnostics.HealthChecks.Abstractions.csproj | 2 +- .../Abstractions/src/baseline.netcore.json | 5 - .../HealthChecks/Directory.Build.props | 7 - ...Extensions.Diagnostics.HealthChecks.csproj | 2 +- .../HealthChecks/src/baseline.netcore.json | 5 - ...ions.Diagnostics.HealthChecks.Tests.csproj | 2 +- src/JSInterop/Directory.Build.props | 7 - .../src/Microsoft.JSInterop.csproj | 2 +- .../src/Mono.WebAssembly.Interop.csproj | 2 - ...xtensions.Localization.Abstractions.csproj | 2 +- .../Abstractions/src/baseline.netcore.json | 413 ----------- .../Microsoft.Extensions.Localization.csproj | 2 +- .../Localization/src/baseline.netcore.json | 687 ------------------ src/ObjectPool/Directory.Build.props | 7 - .../Microsoft.Extensions.ObjectPool.csproj | 2 +- src/ObjectPool/src/baseline.netcore.json | 612 ---------------- .../src/Microsoft.AspNetCore.Testing.csproj | 1 - src/WebEncoders/Directory.Build.props | 7 - .../Microsoft.Extensions.WebEncoders.csproj | 2 +- src/WebEncoders/src/baseline.netcore.json | 564 -------------- 26 files changed, 11 insertions(+), 2686 deletions(-) delete mode 100644 src/FileProviders/Embedded/Directory.Build.props delete mode 100644 src/FileProviders/Embedded/src/baseline.netcore.json delete mode 100644 src/HealthChecks/Abstractions/Directory.Build.props delete mode 100644 src/HealthChecks/Abstractions/src/baseline.netcore.json delete mode 100644 src/HealthChecks/HealthChecks/Directory.Build.props delete mode 100644 src/HealthChecks/HealthChecks/src/baseline.netcore.json delete mode 100644 src/JSInterop/Directory.Build.props delete mode 100644 src/Localization/Abstractions/src/baseline.netcore.json delete mode 100644 src/Localization/Localization/src/baseline.netcore.json delete mode 100644 src/ObjectPool/Directory.Build.props delete mode 100644 src/ObjectPool/src/baseline.netcore.json delete mode 100644 src/WebEncoders/Directory.Build.props delete mode 100644 src/WebEncoders/src/baseline.netcore.json diff --git a/src/Configuration.KeyPerFile/src/Microsoft.Extensions.Configuration.KeyPerFile.csproj b/src/Configuration.KeyPerFile/src/Microsoft.Extensions.Configuration.KeyPerFile.csproj index 82784d0eed..5bd7b2c7ef 100644 --- a/src/Configuration.KeyPerFile/src/Microsoft.Extensions.Configuration.KeyPerFile.csproj +++ b/src/Configuration.KeyPerFile/src/Microsoft.Extensions.Configuration.KeyPerFile.csproj @@ -3,8 +3,7 @@ Configuration provider that uses files in a directory for Microsoft.Extensions.Configuration. netstandard2.0 - false - true + true diff --git a/src/FileProviders/Embedded/Directory.Build.props b/src/FileProviders/Embedded/Directory.Build.props deleted file mode 100644 index f25c1d90ce..0000000000 --- a/src/FileProviders/Embedded/Directory.Build.props +++ /dev/null @@ -1,7 +0,0 @@ - - - - - true - - diff --git a/src/FileProviders/Embedded/src/Microsoft.Extensions.FileProviders.Embedded.csproj b/src/FileProviders/Embedded/src/Microsoft.Extensions.FileProviders.Embedded.csproj index 90cb2e4cc3..792b9ff5b3 100644 --- a/src/FileProviders/Embedded/src/Microsoft.Extensions.FileProviders.Embedded.csproj +++ b/src/FileProviders/Embedded/src/Microsoft.Extensions.FileProviders.Embedded.csproj @@ -5,7 +5,7 @@ File provider for files in embedded resources for Microsoft.Extensions.FileProviders. netstandard2.0 $(MSBuildProjectName).nuspec - true + true diff --git a/src/FileProviders/Embedded/src/baseline.netcore.json b/src/FileProviders/Embedded/src/baseline.netcore.json deleted file mode 100644 index 821969ea0b..0000000000 --- a/src/FileProviders/Embedded/src/baseline.netcore.json +++ /dev/null @@ -1,343 +0,0 @@ -{ - "AssemblyIdentity": "Microsoft.Extensions.FileProviders.Embedded, Version=2.1.1.0, Culture=neutral, PublicKeyToken=adb9793829ddae60", - "Types": [ - { - "Name": "Microsoft.Extensions.FileProviders.EmbeddedFileProvider", - "Visibility": "Public", - "Kind": "Class", - "ImplementedInterfaces": [ - "Microsoft.Extensions.FileProviders.IFileProvider" - ], - "Members": [ - { - "Kind": "Method", - "Name": "GetFileInfo", - "Parameters": [ - { - "Name": "subpath", - "Type": "System.String" - } - ], - "ReturnType": "Microsoft.Extensions.FileProviders.IFileInfo", - "Sealed": true, - "Virtual": true, - "ImplementedInterface": "Microsoft.Extensions.FileProviders.IFileProvider", - "Visibility": "Public", - "GenericParameter": [] - }, - { - "Kind": "Method", - "Name": "GetDirectoryContents", - "Parameters": [ - { - "Name": "subpath", - "Type": "System.String" - } - ], - "ReturnType": "Microsoft.Extensions.FileProviders.IDirectoryContents", - "Sealed": true, - "Virtual": true, - "ImplementedInterface": "Microsoft.Extensions.FileProviders.IFileProvider", - "Visibility": "Public", - "GenericParameter": [] - }, - { - "Kind": "Method", - "Name": "Watch", - "Parameters": [ - { - "Name": "pattern", - "Type": "System.String" - } - ], - "ReturnType": "Microsoft.Extensions.Primitives.IChangeToken", - "Sealed": true, - "Virtual": true, - "ImplementedInterface": "Microsoft.Extensions.FileProviders.IFileProvider", - "Visibility": "Public", - "GenericParameter": [] - }, - { - "Kind": "Constructor", - "Name": ".ctor", - "Parameters": [ - { - "Name": "assembly", - "Type": "System.Reflection.Assembly" - } - ], - "Visibility": "Public", - "GenericParameter": [] - }, - { - "Kind": "Constructor", - "Name": ".ctor", - "Parameters": [ - { - "Name": "assembly", - "Type": "System.Reflection.Assembly" - }, - { - "Name": "baseNamespace", - "Type": "System.String" - } - ], - "Visibility": "Public", - "GenericParameter": [] - } - ], - "GenericParameters": [] - }, - { - "Name": "Microsoft.Extensions.FileProviders.ManifestEmbeddedFileProvider", - "Visibility": "Public", - "Kind": "Class", - "ImplementedInterfaces": [ - "Microsoft.Extensions.FileProviders.IFileProvider" - ], - "Members": [ - { - "Kind": "Method", - "Name": "get_Assembly", - "Parameters": [], - "ReturnType": "System.Reflection.Assembly", - "Visibility": "Public", - "GenericParameter": [] - }, - { - "Kind": "Method", - "Name": "GetDirectoryContents", - "Parameters": [ - { - "Name": "subpath", - "Type": "System.String" - } - ], - "ReturnType": "Microsoft.Extensions.FileProviders.IDirectoryContents", - "Sealed": true, - "Virtual": true, - "ImplementedInterface": "Microsoft.Extensions.FileProviders.IFileProvider", - "Visibility": "Public", - "GenericParameter": [] - }, - { - "Kind": "Method", - "Name": "GetFileInfo", - "Parameters": [ - { - "Name": "subpath", - "Type": "System.String" - } - ], - "ReturnType": "Microsoft.Extensions.FileProviders.IFileInfo", - "Sealed": true, - "Virtual": true, - "ImplementedInterface": "Microsoft.Extensions.FileProviders.IFileProvider", - "Visibility": "Public", - "GenericParameter": [] - }, - { - "Kind": "Method", - "Name": "Watch", - "Parameters": [ - { - "Name": "filter", - "Type": "System.String" - } - ], - "ReturnType": "Microsoft.Extensions.Primitives.IChangeToken", - "Sealed": true, - "Virtual": true, - "ImplementedInterface": "Microsoft.Extensions.FileProviders.IFileProvider", - "Visibility": "Public", - "GenericParameter": [] - }, - { - "Kind": "Constructor", - "Name": ".ctor", - "Parameters": [ - { - "Name": "assembly", - "Type": "System.Reflection.Assembly" - } - ], - "Visibility": "Public", - "GenericParameter": [] - }, - { - "Kind": "Constructor", - "Name": ".ctor", - "Parameters": [ - { - "Name": "assembly", - "Type": "System.Reflection.Assembly" - }, - { - "Name": "root", - "Type": "System.String" - } - ], - "Visibility": "Public", - "GenericParameter": [] - }, - { - "Kind": "Constructor", - "Name": ".ctor", - "Parameters": [ - { - "Name": "assembly", - "Type": "System.Reflection.Assembly" - }, - { - "Name": "root", - "Type": "System.String" - }, - { - "Name": "lastModified", - "Type": "System.DateTimeOffset" - } - ], - "Visibility": "Public", - "GenericParameter": [] - }, - { - "Kind": "Constructor", - "Name": ".ctor", - "Parameters": [ - { - "Name": "assembly", - "Type": "System.Reflection.Assembly" - }, - { - "Name": "root", - "Type": "System.String" - }, - { - "Name": "manifestName", - "Type": "System.String" - }, - { - "Name": "lastModified", - "Type": "System.DateTimeOffset" - } - ], - "Visibility": "Public", - "GenericParameter": [] - } - ], - "GenericParameters": [] - }, - { - "Name": "Microsoft.Extensions.FileProviders.Embedded.EmbeddedResourceFileInfo", - "Visibility": "Public", - "Kind": "Class", - "ImplementedInterfaces": [ - "Microsoft.Extensions.FileProviders.IFileInfo" - ], - "Members": [ - { - "Kind": "Method", - "Name": "get_Exists", - "Parameters": [], - "ReturnType": "System.Boolean", - "Sealed": true, - "Virtual": true, - "ImplementedInterface": "Microsoft.Extensions.FileProviders.IFileInfo", - "Visibility": "Public", - "GenericParameter": [] - }, - { - "Kind": "Method", - "Name": "get_Length", - "Parameters": [], - "ReturnType": "System.Int64", - "Sealed": true, - "Virtual": true, - "ImplementedInterface": "Microsoft.Extensions.FileProviders.IFileInfo", - "Visibility": "Public", - "GenericParameter": [] - }, - { - "Kind": "Method", - "Name": "get_PhysicalPath", - "Parameters": [], - "ReturnType": "System.String", - "Sealed": true, - "Virtual": true, - "ImplementedInterface": "Microsoft.Extensions.FileProviders.IFileInfo", - "Visibility": "Public", - "GenericParameter": [] - }, - { - "Kind": "Method", - "Name": "get_Name", - "Parameters": [], - "ReturnType": "System.String", - "Sealed": true, - "Virtual": true, - "ImplementedInterface": "Microsoft.Extensions.FileProviders.IFileInfo", - "Visibility": "Public", - "GenericParameter": [] - }, - { - "Kind": "Method", - "Name": "get_LastModified", - "Parameters": [], - "ReturnType": "System.DateTimeOffset", - "Sealed": true, - "Virtual": true, - "ImplementedInterface": "Microsoft.Extensions.FileProviders.IFileInfo", - "Visibility": "Public", - "GenericParameter": [] - }, - { - "Kind": "Method", - "Name": "get_IsDirectory", - "Parameters": [], - "ReturnType": "System.Boolean", - "Sealed": true, - "Virtual": true, - "ImplementedInterface": "Microsoft.Extensions.FileProviders.IFileInfo", - "Visibility": "Public", - "GenericParameter": [] - }, - { - "Kind": "Method", - "Name": "CreateReadStream", - "Parameters": [], - "ReturnType": "System.IO.Stream", - "Sealed": true, - "Virtual": true, - "ImplementedInterface": "Microsoft.Extensions.FileProviders.IFileInfo", - "Visibility": "Public", - "GenericParameter": [] - }, - { - "Kind": "Constructor", - "Name": ".ctor", - "Parameters": [ - { - "Name": "assembly", - "Type": "System.Reflection.Assembly" - }, - { - "Name": "resourcePath", - "Type": "System.String" - }, - { - "Name": "name", - "Type": "System.String" - }, - { - "Name": "lastModified", - "Type": "System.DateTimeOffset" - } - ], - "Visibility": "Public", - "GenericParameter": [] - } - ], - "GenericParameters": [] - } - ] -} \ No newline at end of file diff --git a/src/FileProviders/Manifest.MSBuildTask/src/Microsoft.Extensions.FileProviders.Embedded.Manifest.Task.csproj b/src/FileProviders/Manifest.MSBuildTask/src/Microsoft.Extensions.FileProviders.Embedded.Manifest.Task.csproj index 018cc98a8e..f8ae098eab 100644 --- a/src/FileProviders/Manifest.MSBuildTask/src/Microsoft.Extensions.FileProviders.Embedded.Manifest.Task.csproj +++ b/src/FileProviders/Manifest.MSBuildTask/src/Microsoft.Extensions.FileProviders.Embedded.Manifest.Task.csproj @@ -5,7 +5,7 @@ metadata of the files embedded in the assembly at compilation time. netstandard2.0 false - false + true false false diff --git a/src/HealthChecks/Abstractions/Directory.Build.props b/src/HealthChecks/Abstractions/Directory.Build.props deleted file mode 100644 index f25c1d90ce..0000000000 --- a/src/HealthChecks/Abstractions/Directory.Build.props +++ /dev/null @@ -1,7 +0,0 @@ - - - - - true - - diff --git a/src/HealthChecks/Abstractions/src/Microsoft.Extensions.Diagnostics.HealthChecks.Abstractions.csproj b/src/HealthChecks/Abstractions/src/Microsoft.Extensions.Diagnostics.HealthChecks.Abstractions.csproj index 69298be027..2bba5959a3 100644 --- a/src/HealthChecks/Abstractions/src/Microsoft.Extensions.Diagnostics.HealthChecks.Abstractions.csproj +++ b/src/HealthChecks/Abstractions/src/Microsoft.Extensions.Diagnostics.HealthChecks.Abstractions.csproj @@ -11,7 +11,7 @@ Microsoft.Extensions.Diagnostics.HealthChecks.IHealthCheck $(NoWarn);CS1591 true diagnostics;healthchecks - true + true diff --git a/src/HealthChecks/Abstractions/src/baseline.netcore.json b/src/HealthChecks/Abstractions/src/baseline.netcore.json deleted file mode 100644 index 871db4c089..0000000000 --- a/src/HealthChecks/Abstractions/src/baseline.netcore.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "AssemblyIdentity": "Microsoft.Extensions.Diagnostics.HealthChecks.Abstractions, Version=2.1.1.0, Culture=neutral, PublicKeyToken=adb9793829ddae60", - "Types": [ - ] -} \ No newline at end of file diff --git a/src/HealthChecks/HealthChecks/Directory.Build.props b/src/HealthChecks/HealthChecks/Directory.Build.props deleted file mode 100644 index f25c1d90ce..0000000000 --- a/src/HealthChecks/HealthChecks/Directory.Build.props +++ /dev/null @@ -1,7 +0,0 @@ - - - - - true - - diff --git a/src/HealthChecks/HealthChecks/src/Microsoft.Extensions.Diagnostics.HealthChecks.csproj b/src/HealthChecks/HealthChecks/src/Microsoft.Extensions.Diagnostics.HealthChecks.csproj index b49f194d0c..463e5b3632 100644 --- a/src/HealthChecks/HealthChecks/src/Microsoft.Extensions.Diagnostics.HealthChecks.csproj +++ b/src/HealthChecks/HealthChecks/src/Microsoft.Extensions.Diagnostics.HealthChecks.csproj @@ -10,7 +10,7 @@ Microsoft.Extensions.Diagnostics.HealthChecks.IHealthChecksBuilder $(NoWarn);CS1591 true diagnostics;healthchecks - true + true diff --git a/src/HealthChecks/HealthChecks/src/baseline.netcore.json b/src/HealthChecks/HealthChecks/src/baseline.netcore.json deleted file mode 100644 index cb2fe053f1..0000000000 --- a/src/HealthChecks/HealthChecks/src/baseline.netcore.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "AssemblyIdentity": "Microsoft.Extensions.Diagnostics.HealthChecks, Version=2.1.1.0, Culture=neutral, PublicKeyToken=adb9793829ddae60", - "Types": [ - ] -} \ No newline at end of file diff --git a/src/HealthChecks/HealthChecks/test/Microsoft.Extensions.Diagnostics.HealthChecks.Tests.csproj b/src/HealthChecks/HealthChecks/test/Microsoft.Extensions.Diagnostics.HealthChecks.Tests.csproj index 56b7e7e81b..163b618900 100644 --- a/src/HealthChecks/HealthChecks/test/Microsoft.Extensions.Diagnostics.HealthChecks.Tests.csproj +++ b/src/HealthChecks/HealthChecks/test/Microsoft.Extensions.Diagnostics.HealthChecks.Tests.csproj @@ -1,6 +1,6 @@  - + netcoreapp3.0;net472 diff --git a/src/JSInterop/Directory.Build.props b/src/JSInterop/Directory.Build.props deleted file mode 100644 index f25c1d90ce..0000000000 --- a/src/JSInterop/Directory.Build.props +++ /dev/null @@ -1,7 +0,0 @@ - - - - - true - - diff --git a/src/JSInterop/Microsoft.JSInterop/src/Microsoft.JSInterop.csproj b/src/JSInterop/Microsoft.JSInterop/src/Microsoft.JSInterop.csproj index 096b5d5bdd..f92b8d457d 100644 --- a/src/JSInterop/Microsoft.JSInterop/src/Microsoft.JSInterop.csproj +++ b/src/JSInterop/Microsoft.JSInterop/src/Microsoft.JSInterop.csproj @@ -5,7 +5,7 @@ Abstractions and features for interop between .NET and JavaScript code. javascript;interop true - true + true diff --git a/src/JSInterop/Mono.WebAssembly.Interop/src/Mono.WebAssembly.Interop.csproj b/src/JSInterop/Mono.WebAssembly.Interop/src/Mono.WebAssembly.Interop.csproj index 75c8272e95..0ad8effd80 100644 --- a/src/JSInterop/Mono.WebAssembly.Interop/src/Mono.WebAssembly.Interop.csproj +++ b/src/JSInterop/Mono.WebAssembly.Interop/src/Mono.WebAssembly.Interop.csproj @@ -2,8 +2,6 @@ netstandard2.0 - - false false diff --git a/src/Localization/Abstractions/src/Microsoft.Extensions.Localization.Abstractions.csproj b/src/Localization/Abstractions/src/Microsoft.Extensions.Localization.Abstractions.csproj index 636072724f..33f58b6358 100644 --- a/src/Localization/Abstractions/src/Microsoft.Extensions.Localization.Abstractions.csproj +++ b/src/Localization/Abstractions/src/Microsoft.Extensions.Localization.Abstractions.csproj @@ -10,7 +10,7 @@ Microsoft.Extensions.Localization.IStringLocalizer<T> $(NoWarn);CS1591 true localization - true + true diff --git a/src/Localization/Abstractions/src/baseline.netcore.json b/src/Localization/Abstractions/src/baseline.netcore.json deleted file mode 100644 index 02ba71db8e..0000000000 --- a/src/Localization/Abstractions/src/baseline.netcore.json +++ /dev/null @@ -1,413 +0,0 @@ -{ - "AssemblyIdentity": "Microsoft.Extensions.Localization.Abstractions, Version=2.1.1.0, Culture=neutral, PublicKeyToken=adb9793829ddae60", - "Types": [ - { - "Name": "Microsoft.Extensions.Localization.IStringLocalizer", - "Visibility": "Public", - "Kind": "Interface", - "Abstract": true, - "ImplementedInterfaces": [], - "Members": [ - { - "Kind": "Method", - "Name": "get_Item", - "Parameters": [ - { - "Name": "name", - "Type": "System.String" - } - ], - "ReturnType": "Microsoft.Extensions.Localization.LocalizedString", - "GenericParameter": [] - }, - { - "Kind": "Method", - "Name": "get_Item", - "Parameters": [ - { - "Name": "name", - "Type": "System.String" - }, - { - "Name": "arguments", - "Type": "System.Object[]", - "IsParams": true - } - ], - "ReturnType": "Microsoft.Extensions.Localization.LocalizedString", - "GenericParameter": [] - }, - { - "Kind": "Method", - "Name": "GetAllStrings", - "Parameters": [ - { - "Name": "includeParentCultures", - "Type": "System.Boolean" - } - ], - "ReturnType": "System.Collections.Generic.IEnumerable", - "GenericParameter": [] - }, - { - "Kind": "Method", - "Name": "WithCulture", - "Parameters": [ - { - "Name": "culture", - "Type": "System.Globalization.CultureInfo" - } - ], - "ReturnType": "Microsoft.Extensions.Localization.IStringLocalizer", - "GenericParameter": [] - } - ], - "GenericParameters": [] - }, - { - "Name": "Microsoft.Extensions.Localization.IStringLocalizerFactory", - "Visibility": "Public", - "Kind": "Interface", - "Abstract": true, - "ImplementedInterfaces": [], - "Members": [ - { - "Kind": "Method", - "Name": "Create", - "Parameters": [ - { - "Name": "resourceSource", - "Type": "System.Type" - } - ], - "ReturnType": "Microsoft.Extensions.Localization.IStringLocalizer", - "GenericParameter": [] - }, - { - "Kind": "Method", - "Name": "Create", - "Parameters": [ - { - "Name": "baseName", - "Type": "System.String" - }, - { - "Name": "location", - "Type": "System.String" - } - ], - "ReturnType": "Microsoft.Extensions.Localization.IStringLocalizer", - "GenericParameter": [] - } - ], - "GenericParameters": [] - }, - { - "Name": "Microsoft.Extensions.Localization.IStringLocalizer", - "Visibility": "Public", - "Kind": "Interface", - "Abstract": true, - "ImplementedInterfaces": [ - "Microsoft.Extensions.Localization.IStringLocalizer" - ], - "Members": [], - "GenericParameters": [ - { - "ParameterName": "T", - "ParameterPosition": 0, - "BaseTypeOrInterfaces": [] - } - ] - }, - { - "Name": "Microsoft.Extensions.Localization.LocalizedString", - "Visibility": "Public", - "Kind": "Class", - "ImplementedInterfaces": [], - "Members": [ - { - "Kind": "Method", - "Name": "op_Implicit", - "Parameters": [ - { - "Name": "localizedString", - "Type": "Microsoft.Extensions.Localization.LocalizedString" - } - ], - "ReturnType": "System.String", - "Static": true, - "Visibility": "Public", - "GenericParameter": [] - }, - { - "Kind": "Method", - "Name": "get_Name", - "Parameters": [], - "ReturnType": "System.String", - "Visibility": "Public", - "GenericParameter": [] - }, - { - "Kind": "Method", - "Name": "get_Value", - "Parameters": [], - "ReturnType": "System.String", - "Visibility": "Public", - "GenericParameter": [] - }, - { - "Kind": "Method", - "Name": "get_ResourceNotFound", - "Parameters": [], - "ReturnType": "System.Boolean", - "Visibility": "Public", - "GenericParameter": [] - }, - { - "Kind": "Method", - "Name": "get_SearchedLocation", - "Parameters": [], - "ReturnType": "System.String", - "Visibility": "Public", - "GenericParameter": [] - }, - { - "Kind": "Method", - "Name": "ToString", - "Parameters": [], - "ReturnType": "System.String", - "Virtual": true, - "Override": true, - "Visibility": "Public", - "GenericParameter": [] - }, - { - "Kind": "Constructor", - "Name": ".ctor", - "Parameters": [ - { - "Name": "name", - "Type": "System.String" - }, - { - "Name": "value", - "Type": "System.String" - } - ], - "Visibility": "Public", - "GenericParameter": [] - }, - { - "Kind": "Constructor", - "Name": ".ctor", - "Parameters": [ - { - "Name": "name", - "Type": "System.String" - }, - { - "Name": "value", - "Type": "System.String" - }, - { - "Name": "resourceNotFound", - "Type": "System.Boolean" - } - ], - "Visibility": "Public", - "GenericParameter": [] - }, - { - "Kind": "Constructor", - "Name": ".ctor", - "Parameters": [ - { - "Name": "name", - "Type": "System.String" - }, - { - "Name": "value", - "Type": "System.String" - }, - { - "Name": "resourceNotFound", - "Type": "System.Boolean" - }, - { - "Name": "searchedLocation", - "Type": "System.String" - } - ], - "Visibility": "Public", - "GenericParameter": [] - } - ], - "GenericParameters": [] - }, - { - "Name": "Microsoft.Extensions.Localization.StringLocalizerExtensions", - "Visibility": "Public", - "Kind": "Class", - "Abstract": true, - "Static": true, - "Sealed": true, - "ImplementedInterfaces": [], - "Members": [ - { - "Kind": "Method", - "Name": "GetString", - "Parameters": [ - { - "Name": "stringLocalizer", - "Type": "Microsoft.Extensions.Localization.IStringLocalizer" - }, - { - "Name": "name", - "Type": "System.String" - } - ], - "ReturnType": "Microsoft.Extensions.Localization.LocalizedString", - "Static": true, - "Extension": true, - "Visibility": "Public", - "GenericParameter": [] - }, - { - "Kind": "Method", - "Name": "GetString", - "Parameters": [ - { - "Name": "stringLocalizer", - "Type": "Microsoft.Extensions.Localization.IStringLocalizer" - }, - { - "Name": "name", - "Type": "System.String" - }, - { - "Name": "arguments", - "Type": "System.Object[]", - "IsParams": true - } - ], - "ReturnType": "Microsoft.Extensions.Localization.LocalizedString", - "Static": true, - "Extension": true, - "Visibility": "Public", - "GenericParameter": [] - }, - { - "Kind": "Method", - "Name": "GetAllStrings", - "Parameters": [ - { - "Name": "stringLocalizer", - "Type": "Microsoft.Extensions.Localization.IStringLocalizer" - } - ], - "ReturnType": "System.Collections.Generic.IEnumerable", - "Static": true, - "Extension": true, - "Visibility": "Public", - "GenericParameter": [] - } - ], - "GenericParameters": [] - }, - { - "Name": "Microsoft.Extensions.Localization.StringLocalizer", - "Visibility": "Public", - "Kind": "Class", - "ImplementedInterfaces": [ - "Microsoft.Extensions.Localization.IStringLocalizer" - ], - "Members": [ - { - "Kind": "Method", - "Name": "get_Item", - "Parameters": [ - { - "Name": "name", - "Type": "System.String" - } - ], - "ReturnType": "Microsoft.Extensions.Localization.LocalizedString", - "Virtual": true, - "ImplementedInterface": "Microsoft.Extensions.Localization.IStringLocalizer", - "Visibility": "Public", - "GenericParameter": [] - }, - { - "Kind": "Method", - "Name": "get_Item", - "Parameters": [ - { - "Name": "name", - "Type": "System.String" - }, - { - "Name": "arguments", - "Type": "System.Object[]", - "IsParams": true - } - ], - "ReturnType": "Microsoft.Extensions.Localization.LocalizedString", - "Virtual": true, - "ImplementedInterface": "Microsoft.Extensions.Localization.IStringLocalizer", - "Visibility": "Public", - "GenericParameter": [] - }, - { - "Kind": "Method", - "Name": "GetAllStrings", - "Parameters": [ - { - "Name": "includeParentCultures", - "Type": "System.Boolean" - } - ], - "ReturnType": "System.Collections.Generic.IEnumerable", - "Sealed": true, - "Virtual": true, - "ImplementedInterface": "Microsoft.Extensions.Localization.IStringLocalizer", - "Visibility": "Public", - "GenericParameter": [] - }, - { - "Kind": "Method", - "Name": "WithCulture", - "Parameters": [ - { - "Name": "culture", - "Type": "System.Globalization.CultureInfo" - } - ], - "ReturnType": "Microsoft.Extensions.Localization.IStringLocalizer", - "Virtual": true, - "ImplementedInterface": "Microsoft.Extensions.Localization.IStringLocalizer", - "Visibility": "Public", - "GenericParameter": [] - }, - { - "Kind": "Constructor", - "Name": ".ctor", - "Parameters": [ - { - "Name": "factory", - "Type": "Microsoft.Extensions.Localization.IStringLocalizerFactory" - } - ], - "Visibility": "Public", - "GenericParameter": [] - } - ], - "GenericParameters": [ - { - "ParameterName": "TResourceSource", - "ParameterPosition": 0, - "BaseTypeOrInterfaces": [] - } - ] - } - ] -} \ No newline at end of file diff --git a/src/Localization/Localization/src/Microsoft.Extensions.Localization.csproj b/src/Localization/Localization/src/Microsoft.Extensions.Localization.csproj index 8a820ac7b9..f16cbd9dab 100644 --- a/src/Localization/Localization/src/Microsoft.Extensions.Localization.csproj +++ b/src/Localization/Localization/src/Microsoft.Extensions.Localization.csproj @@ -7,7 +7,7 @@ $(NoWarn);CS1591 true localization - true + true diff --git a/src/Localization/Localization/src/baseline.netcore.json b/src/Localization/Localization/src/baseline.netcore.json deleted file mode 100644 index 860db76899..0000000000 --- a/src/Localization/Localization/src/baseline.netcore.json +++ /dev/null @@ -1,687 +0,0 @@ -{ - "AssemblyIdentity": "Microsoft.Extensions.Localization, Version=2.1.1.0, Culture=neutral, PublicKeyToken=adb9793829ddae60", - "Types": [ - { - "Name": "Microsoft.Extensions.DependencyInjection.LocalizationServiceCollectionExtensions", - "Visibility": "Public", - "Kind": "Class", - "Abstract": true, - "Static": true, - "Sealed": true, - "ImplementedInterfaces": [], - "Members": [ - { - "Kind": "Method", - "Name": "AddLocalization", - "Parameters": [ - { - "Name": "services", - "Type": "Microsoft.Extensions.DependencyInjection.IServiceCollection" - } - ], - "ReturnType": "Microsoft.Extensions.DependencyInjection.IServiceCollection", - "Static": true, - "Extension": true, - "Visibility": "Public", - "GenericParameter": [] - }, - { - "Kind": "Method", - "Name": "AddLocalization", - "Parameters": [ - { - "Name": "services", - "Type": "Microsoft.Extensions.DependencyInjection.IServiceCollection" - }, - { - "Name": "setupAction", - "Type": "System.Action" - } - ], - "ReturnType": "Microsoft.Extensions.DependencyInjection.IServiceCollection", - "Static": true, - "Extension": true, - "Visibility": "Public", - "GenericParameter": [] - } - ], - "GenericParameters": [] - }, - { - "Name": "Microsoft.Extensions.Localization.IResourceNamesCache", - "Visibility": "Public", - "Kind": "Interface", - "Abstract": true, - "ImplementedInterfaces": [], - "Members": [ - { - "Kind": "Method", - "Name": "GetOrAdd", - "Parameters": [ - { - "Name": "name", - "Type": "System.String" - }, - { - "Name": "valueFactory", - "Type": "System.Func>" - } - ], - "ReturnType": "System.Collections.Generic.IList", - "GenericParameter": [] - } - ], - "GenericParameters": [] - }, - { - "Name": "Microsoft.Extensions.Localization.LocalizationOptions", - "Visibility": "Public", - "Kind": "Class", - "ImplementedInterfaces": [], - "Members": [ - { - "Kind": "Method", - "Name": "get_ResourcesPath", - "Parameters": [], - "ReturnType": "System.String", - "Visibility": "Public", - "GenericParameter": [] - }, - { - "Kind": "Method", - "Name": "set_ResourcesPath", - "Parameters": [ - { - "Name": "value", - "Type": "System.String" - } - ], - "ReturnType": "System.Void", - "Visibility": "Public", - "GenericParameter": [] - }, - { - "Kind": "Constructor", - "Name": ".ctor", - "Parameters": [], - "Visibility": "Public", - "GenericParameter": [] - } - ], - "GenericParameters": [] - }, - { - "Name": "Microsoft.Extensions.Localization.ResourceLocationAttribute", - "Visibility": "Public", - "Kind": "Class", - "BaseType": "System.Attribute", - "ImplementedInterfaces": [], - "Members": [ - { - "Kind": "Method", - "Name": "get_ResourceLocation", - "Parameters": [], - "ReturnType": "System.String", - "Visibility": "Public", - "GenericParameter": [] - }, - { - "Kind": "Constructor", - "Name": ".ctor", - "Parameters": [ - { - "Name": "resourceLocation", - "Type": "System.String" - } - ], - "Visibility": "Public", - "GenericParameter": [] - } - ], - "GenericParameters": [] - }, - { - "Name": "Microsoft.Extensions.Localization.ResourceManagerStringLocalizer", - "Visibility": "Public", - "Kind": "Class", - "ImplementedInterfaces": [ - "Microsoft.Extensions.Localization.IStringLocalizer" - ], - "Members": [ - { - "Kind": "Method", - "Name": "get_Item", - "Parameters": [ - { - "Name": "name", - "Type": "System.String" - } - ], - "ReturnType": "Microsoft.Extensions.Localization.LocalizedString", - "Virtual": true, - "ImplementedInterface": "Microsoft.Extensions.Localization.IStringLocalizer", - "Visibility": "Public", - "GenericParameter": [] - }, - { - "Kind": "Method", - "Name": "get_Item", - "Parameters": [ - { - "Name": "name", - "Type": "System.String" - }, - { - "Name": "arguments", - "Type": "System.Object[]", - "IsParams": true - } - ], - "ReturnType": "Microsoft.Extensions.Localization.LocalizedString", - "Virtual": true, - "ImplementedInterface": "Microsoft.Extensions.Localization.IStringLocalizer", - "Visibility": "Public", - "GenericParameter": [] - }, - { - "Kind": "Method", - "Name": "WithCulture", - "Parameters": [ - { - "Name": "culture", - "Type": "System.Globalization.CultureInfo" - } - ], - "ReturnType": "Microsoft.Extensions.Localization.IStringLocalizer", - "Sealed": true, - "Virtual": true, - "ImplementedInterface": "Microsoft.Extensions.Localization.IStringLocalizer", - "Visibility": "Public", - "GenericParameter": [] - }, - { - "Kind": "Method", - "Name": "GetAllStrings", - "Parameters": [ - { - "Name": "includeParentCultures", - "Type": "System.Boolean" - } - ], - "ReturnType": "System.Collections.Generic.IEnumerable", - "Virtual": true, - "ImplementedInterface": "Microsoft.Extensions.Localization.IStringLocalizer", - "Visibility": "Public", - "GenericParameter": [] - }, - { - "Kind": "Method", - "Name": "GetAllStrings", - "Parameters": [ - { - "Name": "includeParentCultures", - "Type": "System.Boolean" - }, - { - "Name": "culture", - "Type": "System.Globalization.CultureInfo" - } - ], - "ReturnType": "System.Collections.Generic.IEnumerable", - "Visibility": "Protected", - "GenericParameter": [] - }, - { - "Kind": "Method", - "Name": "GetStringSafely", - "Parameters": [ - { - "Name": "name", - "Type": "System.String" - }, - { - "Name": "culture", - "Type": "System.Globalization.CultureInfo" - } - ], - "ReturnType": "System.String", - "Visibility": "Protected", - "GenericParameter": [] - }, - { - "Kind": "Constructor", - "Name": ".ctor", - "Parameters": [ - { - "Name": "resourceManager", - "Type": "System.Resources.ResourceManager" - }, - { - "Name": "resourceAssembly", - "Type": "System.Reflection.Assembly" - }, - { - "Name": "baseName", - "Type": "System.String" - }, - { - "Name": "resourceNamesCache", - "Type": "Microsoft.Extensions.Localization.IResourceNamesCache" - }, - { - "Name": "logger", - "Type": "Microsoft.Extensions.Logging.ILogger" - } - ], - "Visibility": "Public", - "GenericParameter": [] - }, - { - "Kind": "Constructor", - "Name": ".ctor", - "Parameters": [ - { - "Name": "resourceManager", - "Type": "System.Resources.ResourceManager" - }, - { - "Name": "resourceAssemblyWrapper", - "Type": "Microsoft.Extensions.Localization.Internal.AssemblyWrapper" - }, - { - "Name": "baseName", - "Type": "System.String" - }, - { - "Name": "resourceNamesCache", - "Type": "Microsoft.Extensions.Localization.IResourceNamesCache" - }, - { - "Name": "logger", - "Type": "Microsoft.Extensions.Logging.ILogger" - } - ], - "Visibility": "Public", - "GenericParameter": [] - }, - { - "Kind": "Constructor", - "Name": ".ctor", - "Parameters": [ - { - "Name": "resourceManager", - "Type": "System.Resources.ResourceManager" - }, - { - "Name": "resourceStringProvider", - "Type": "Microsoft.Extensions.Localization.Internal.IResourceStringProvider" - }, - { - "Name": "baseName", - "Type": "System.String" - }, - { - "Name": "resourceNamesCache", - "Type": "Microsoft.Extensions.Localization.IResourceNamesCache" - }, - { - "Name": "logger", - "Type": "Microsoft.Extensions.Logging.ILogger" - } - ], - "Visibility": "Public", - "GenericParameter": [] - } - ], - "GenericParameters": [] - }, - { - "Name": "Microsoft.Extensions.Localization.ResourceManagerStringLocalizerFactory", - "Visibility": "Public", - "Kind": "Class", - "ImplementedInterfaces": [ - "Microsoft.Extensions.Localization.IStringLocalizerFactory" - ], - "Members": [ - { - "Kind": "Method", - "Name": "GetResourcePrefix", - "Parameters": [ - { - "Name": "typeInfo", - "Type": "System.Reflection.TypeInfo" - } - ], - "ReturnType": "System.String", - "Virtual": true, - "Visibility": "Protected", - "GenericParameter": [] - }, - { - "Kind": "Method", - "Name": "GetResourcePrefix", - "Parameters": [ - { - "Name": "typeInfo", - "Type": "System.Reflection.TypeInfo" - }, - { - "Name": "baseNamespace", - "Type": "System.String" - }, - { - "Name": "resourcesRelativePath", - "Type": "System.String" - } - ], - "ReturnType": "System.String", - "Virtual": true, - "Visibility": "Protected", - "GenericParameter": [] - }, - { - "Kind": "Method", - "Name": "GetResourcePrefix", - "Parameters": [ - { - "Name": "baseResourceName", - "Type": "System.String" - }, - { - "Name": "baseNamespace", - "Type": "System.String" - } - ], - "ReturnType": "System.String", - "Virtual": true, - "Visibility": "Protected", - "GenericParameter": [] - }, - { - "Kind": "Method", - "Name": "Create", - "Parameters": [ - { - "Name": "resourceSource", - "Type": "System.Type" - } - ], - "ReturnType": "Microsoft.Extensions.Localization.IStringLocalizer", - "Sealed": true, - "Virtual": true, - "ImplementedInterface": "Microsoft.Extensions.Localization.IStringLocalizerFactory", - "Visibility": "Public", - "GenericParameter": [] - }, - { - "Kind": "Method", - "Name": "Create", - "Parameters": [ - { - "Name": "baseName", - "Type": "System.String" - }, - { - "Name": "location", - "Type": "System.String" - } - ], - "ReturnType": "Microsoft.Extensions.Localization.IStringLocalizer", - "Sealed": true, - "Virtual": true, - "ImplementedInterface": "Microsoft.Extensions.Localization.IStringLocalizerFactory", - "Visibility": "Public", - "GenericParameter": [] - }, - { - "Kind": "Method", - "Name": "CreateResourceManagerStringLocalizer", - "Parameters": [ - { - "Name": "assembly", - "Type": "System.Reflection.Assembly" - }, - { - "Name": "baseName", - "Type": "System.String" - } - ], - "ReturnType": "Microsoft.Extensions.Localization.ResourceManagerStringLocalizer", - "Virtual": true, - "Visibility": "Protected", - "GenericParameter": [] - }, - { - "Kind": "Method", - "Name": "GetResourcePrefix", - "Parameters": [ - { - "Name": "location", - "Type": "System.String" - }, - { - "Name": "baseName", - "Type": "System.String" - }, - { - "Name": "resourceLocation", - "Type": "System.String" - } - ], - "ReturnType": "System.String", - "Virtual": true, - "Visibility": "Protected", - "GenericParameter": [] - }, - { - "Kind": "Method", - "Name": "GetResourceLocationAttribute", - "Parameters": [ - { - "Name": "assembly", - "Type": "System.Reflection.Assembly" - } - ], - "ReturnType": "Microsoft.Extensions.Localization.ResourceLocationAttribute", - "Virtual": true, - "Visibility": "Protected", - "GenericParameter": [] - }, - { - "Kind": "Method", - "Name": "GetRootNamespaceAttribute", - "Parameters": [ - { - "Name": "assembly", - "Type": "System.Reflection.Assembly" - } - ], - "ReturnType": "Microsoft.Extensions.Localization.RootNamespaceAttribute", - "Virtual": true, - "Visibility": "Protected", - "GenericParameter": [] - }, - { - "Kind": "Constructor", - "Name": ".ctor", - "Parameters": [ - { - "Name": "localizationOptions", - "Type": "Microsoft.Extensions.Options.IOptions" - }, - { - "Name": "loggerFactory", - "Type": "Microsoft.Extensions.Logging.ILoggerFactory" - } - ], - "Visibility": "Public", - "GenericParameter": [] - } - ], - "GenericParameters": [] - }, - { - "Name": "Microsoft.Extensions.Localization.ResourceManagerWithCultureStringLocalizer", - "Visibility": "Public", - "Kind": "Class", - "BaseType": "Microsoft.Extensions.Localization.ResourceManagerStringLocalizer", - "ImplementedInterfaces": [], - "Members": [ - { - "Kind": "Method", - "Name": "get_Item", - "Parameters": [ - { - "Name": "name", - "Type": "System.String" - } - ], - "ReturnType": "Microsoft.Extensions.Localization.LocalizedString", - "Virtual": true, - "Override": true, - "ImplementedInterface": "Microsoft.Extensions.Localization.IStringLocalizer", - "Visibility": "Public", - "GenericParameter": [] - }, - { - "Kind": "Method", - "Name": "get_Item", - "Parameters": [ - { - "Name": "name", - "Type": "System.String" - }, - { - "Name": "arguments", - "Type": "System.Object[]", - "IsParams": true - } - ], - "ReturnType": "Microsoft.Extensions.Localization.LocalizedString", - "Virtual": true, - "Override": true, - "ImplementedInterface": "Microsoft.Extensions.Localization.IStringLocalizer", - "Visibility": "Public", - "GenericParameter": [] - }, - { - "Kind": "Method", - "Name": "GetAllStrings", - "Parameters": [ - { - "Name": "includeParentCultures", - "Type": "System.Boolean" - } - ], - "ReturnType": "System.Collections.Generic.IEnumerable", - "Virtual": true, - "Override": true, - "ImplementedInterface": "Microsoft.Extensions.Localization.IStringLocalizer", - "Visibility": "Public", - "GenericParameter": [] - }, - { - "Kind": "Constructor", - "Name": ".ctor", - "Parameters": [ - { - "Name": "resourceManager", - "Type": "System.Resources.ResourceManager" - }, - { - "Name": "resourceAssembly", - "Type": "System.Reflection.Assembly" - }, - { - "Name": "baseName", - "Type": "System.String" - }, - { - "Name": "resourceNamesCache", - "Type": "Microsoft.Extensions.Localization.IResourceNamesCache" - }, - { - "Name": "culture", - "Type": "System.Globalization.CultureInfo" - }, - { - "Name": "logger", - "Type": "Microsoft.Extensions.Logging.ILogger" - } - ], - "Visibility": "Public", - "GenericParameter": [] - } - ], - "GenericParameters": [] - }, - { - "Name": "Microsoft.Extensions.Localization.ResourceNamesCache", - "Visibility": "Public", - "Kind": "Class", - "ImplementedInterfaces": [ - "Microsoft.Extensions.Localization.IResourceNamesCache" - ], - "Members": [ - { - "Kind": "Method", - "Name": "GetOrAdd", - "Parameters": [ - { - "Name": "name", - "Type": "System.String" - }, - { - "Name": "valueFactory", - "Type": "System.Func>" - } - ], - "ReturnType": "System.Collections.Generic.IList", - "Sealed": true, - "Virtual": true, - "ImplementedInterface": "Microsoft.Extensions.Localization.IResourceNamesCache", - "Visibility": "Public", - "GenericParameter": [] - }, - { - "Kind": "Constructor", - "Name": ".ctor", - "Parameters": [], - "Visibility": "Public", - "GenericParameter": [] - } - ], - "GenericParameters": [] - }, - { - "Name": "Microsoft.Extensions.Localization.RootNamespaceAttribute", - "Visibility": "Public", - "Kind": "Class", - "BaseType": "System.Attribute", - "ImplementedInterfaces": [], - "Members": [ - { - "Kind": "Method", - "Name": "get_RootNamespace", - "Parameters": [], - "ReturnType": "System.String", - "Visibility": "Public", - "GenericParameter": [] - }, - { - "Kind": "Constructor", - "Name": ".ctor", - "Parameters": [ - { - "Name": "rootNamespace", - "Type": "System.String" - } - ], - "Visibility": "Public", - "GenericParameter": [] - } - ], - "GenericParameters": [] - } - ] -} \ No newline at end of file diff --git a/src/ObjectPool/Directory.Build.props b/src/ObjectPool/Directory.Build.props deleted file mode 100644 index f25c1d90ce..0000000000 --- a/src/ObjectPool/Directory.Build.props +++ /dev/null @@ -1,7 +0,0 @@ - - - - - true - - diff --git a/src/ObjectPool/src/Microsoft.Extensions.ObjectPool.csproj b/src/ObjectPool/src/Microsoft.Extensions.ObjectPool.csproj index 646d18a8b7..71e9abed79 100644 --- a/src/ObjectPool/src/Microsoft.Extensions.ObjectPool.csproj +++ b/src/ObjectPool/src/Microsoft.Extensions.ObjectPool.csproj @@ -6,7 +6,7 @@ $(NoWarn);CS1591 true pooling - true + true diff --git a/src/ObjectPool/src/baseline.netcore.json b/src/ObjectPool/src/baseline.netcore.json deleted file mode 100644 index 253c1f6b66..0000000000 --- a/src/ObjectPool/src/baseline.netcore.json +++ /dev/null @@ -1,612 +0,0 @@ -{ - "AssemblyIdentity": "Microsoft.Extensions.ObjectPool, Version=2.1.1.0, Culture=neutral, PublicKeyToken=adb9793829ddae60", - "Types": [ - { - "Name": "Microsoft.Extensions.ObjectPool.DefaultObjectPool", - "Visibility": "Public", - "Kind": "Class", - "BaseType": "Microsoft.Extensions.ObjectPool.ObjectPool", - "ImplementedInterfaces": [], - "Members": [ - { - "Kind": "Method", - "Name": "Get", - "Parameters": [], - "ReturnType": "T0", - "Virtual": true, - "Override": true, - "Visibility": "Public", - "GenericParameter": [] - }, - { - "Kind": "Method", - "Name": "Return", - "Parameters": [ - { - "Name": "obj", - "Type": "T0" - } - ], - "ReturnType": "System.Void", - "Virtual": true, - "Override": true, - "Visibility": "Public", - "GenericParameter": [] - }, - { - "Kind": "Constructor", - "Name": ".ctor", - "Parameters": [ - { - "Name": "policy", - "Type": "Microsoft.Extensions.ObjectPool.IPooledObjectPolicy" - } - ], - "Visibility": "Public", - "GenericParameter": [] - }, - { - "Kind": "Constructor", - "Name": ".ctor", - "Parameters": [ - { - "Name": "policy", - "Type": "Microsoft.Extensions.ObjectPool.IPooledObjectPolicy" - }, - { - "Name": "maximumRetained", - "Type": "System.Int32" - } - ], - "Visibility": "Public", - "GenericParameter": [] - } - ], - "GenericParameters": [ - { - "ParameterName": "T", - "ParameterPosition": 0, - "Class": true, - "BaseTypeOrInterfaces": [] - } - ] - }, - { - "Name": "Microsoft.Extensions.ObjectPool.DefaultObjectPoolProvider", - "Visibility": "Public", - "Kind": "Class", - "BaseType": "Microsoft.Extensions.ObjectPool.ObjectPoolProvider", - "ImplementedInterfaces": [], - "Members": [ - { - "Kind": "Method", - "Name": "get_MaximumRetained", - "Parameters": [], - "ReturnType": "System.Int32", - "Visibility": "Public", - "GenericParameter": [] - }, - { - "Kind": "Method", - "Name": "set_MaximumRetained", - "Parameters": [ - { - "Name": "value", - "Type": "System.Int32" - } - ], - "ReturnType": "System.Void", - "Visibility": "Public", - "GenericParameter": [] - }, - { - "Kind": "Method", - "Name": "Create", - "Parameters": [ - { - "Name": "policy", - "Type": "Microsoft.Extensions.ObjectPool.IPooledObjectPolicy" - } - ], - "ReturnType": "Microsoft.Extensions.ObjectPool.ObjectPool", - "Virtual": true, - "Override": true, - "Visibility": "Public", - "GenericParameter": [ - { - "ParameterName": "T", - "ParameterPosition": 0, - "Class": true, - "BaseTypeOrInterfaces": [] - } - ] - }, - { - "Kind": "Constructor", - "Name": ".ctor", - "Parameters": [], - "Visibility": "Public", - "GenericParameter": [] - } - ], - "GenericParameters": [] - }, - { - "Name": "Microsoft.Extensions.ObjectPool.DefaultPooledObjectPolicy", - "Visibility": "Public", - "Kind": "Class", - "BaseType": "Microsoft.Extensions.ObjectPool.PooledObjectPolicy", - "ImplementedInterfaces": [], - "Members": [ - { - "Kind": "Method", - "Name": "Create", - "Parameters": [], - "ReturnType": "T0", - "Virtual": true, - "Override": true, - "ImplementedInterface": "Microsoft.Extensions.ObjectPool.IPooledObjectPolicy", - "Visibility": "Public", - "GenericParameter": [] - }, - { - "Kind": "Method", - "Name": "Return", - "Parameters": [ - { - "Name": "obj", - "Type": "T0" - } - ], - "ReturnType": "System.Boolean", - "Virtual": true, - "Override": true, - "ImplementedInterface": "Microsoft.Extensions.ObjectPool.IPooledObjectPolicy", - "Visibility": "Public", - "GenericParameter": [] - }, - { - "Kind": "Constructor", - "Name": ".ctor", - "Parameters": [], - "Visibility": "Public", - "GenericParameter": [] - } - ], - "GenericParameters": [ - { - "ParameterName": "T", - "ParameterPosition": 0, - "New": true, - "Class": true, - "BaseTypeOrInterfaces": [] - } - ] - }, - { - "Name": "Microsoft.Extensions.ObjectPool.IPooledObjectPolicy", - "Visibility": "Public", - "Kind": "Interface", - "Abstract": true, - "ImplementedInterfaces": [], - "Members": [ - { - "Kind": "Method", - "Name": "Create", - "Parameters": [], - "ReturnType": "T0", - "GenericParameter": [] - }, - { - "Kind": "Method", - "Name": "Return", - "Parameters": [ - { - "Name": "obj", - "Type": "T0" - } - ], - "ReturnType": "System.Boolean", - "GenericParameter": [] - } - ], - "GenericParameters": [ - { - "ParameterName": "T", - "ParameterPosition": 0, - "BaseTypeOrInterfaces": [] - } - ] - }, - { - "Name": "Microsoft.Extensions.ObjectPool.LeakTrackingObjectPool", - "Visibility": "Public", - "Kind": "Class", - "BaseType": "Microsoft.Extensions.ObjectPool.ObjectPool", - "ImplementedInterfaces": [], - "Members": [ - { - "Kind": "Method", - "Name": "Get", - "Parameters": [], - "ReturnType": "T0", - "Virtual": true, - "Override": true, - "Visibility": "Public", - "GenericParameter": [] - }, - { - "Kind": "Method", - "Name": "Return", - "Parameters": [ - { - "Name": "obj", - "Type": "T0" - } - ], - "ReturnType": "System.Void", - "Virtual": true, - "Override": true, - "Visibility": "Public", - "GenericParameter": [] - }, - { - "Kind": "Constructor", - "Name": ".ctor", - "Parameters": [ - { - "Name": "inner", - "Type": "Microsoft.Extensions.ObjectPool.ObjectPool" - } - ], - "Visibility": "Public", - "GenericParameter": [] - } - ], - "GenericParameters": [ - { - "ParameterName": "T", - "ParameterPosition": 0, - "Class": true, - "BaseTypeOrInterfaces": [] - } - ] - }, - { - "Name": "Microsoft.Extensions.ObjectPool.LeakTrackingObjectPoolProvider", - "Visibility": "Public", - "Kind": "Class", - "BaseType": "Microsoft.Extensions.ObjectPool.ObjectPoolProvider", - "ImplementedInterfaces": [], - "Members": [ - { - "Kind": "Method", - "Name": "Create", - "Parameters": [ - { - "Name": "policy", - "Type": "Microsoft.Extensions.ObjectPool.IPooledObjectPolicy" - } - ], - "ReturnType": "Microsoft.Extensions.ObjectPool.ObjectPool", - "Virtual": true, - "Override": true, - "Visibility": "Public", - "GenericParameter": [ - { - "ParameterName": "T", - "ParameterPosition": 0, - "Class": true, - "BaseTypeOrInterfaces": [] - } - ] - }, - { - "Kind": "Constructor", - "Name": ".ctor", - "Parameters": [ - { - "Name": "inner", - "Type": "Microsoft.Extensions.ObjectPool.ObjectPoolProvider" - } - ], - "Visibility": "Public", - "GenericParameter": [] - } - ], - "GenericParameters": [] - }, - { - "Name": "Microsoft.Extensions.ObjectPool.ObjectPool", - "Visibility": "Public", - "Kind": "Class", - "Abstract": true, - "ImplementedInterfaces": [], - "Members": [ - { - "Kind": "Method", - "Name": "Get", - "Parameters": [], - "ReturnType": "T0", - "Virtual": true, - "Abstract": true, - "Visibility": "Public", - "GenericParameter": [] - }, - { - "Kind": "Method", - "Name": "Return", - "Parameters": [ - { - "Name": "obj", - "Type": "T0" - } - ], - "ReturnType": "System.Void", - "Virtual": true, - "Abstract": true, - "Visibility": "Public", - "GenericParameter": [] - }, - { - "Kind": "Constructor", - "Name": ".ctor", - "Parameters": [], - "Visibility": "Protected", - "GenericParameter": [] - } - ], - "GenericParameters": [ - { - "ParameterName": "T", - "ParameterPosition": 0, - "Class": true, - "BaseTypeOrInterfaces": [] - } - ] - }, - { - "Name": "Microsoft.Extensions.ObjectPool.ObjectPoolProvider", - "Visibility": "Public", - "Kind": "Class", - "Abstract": true, - "ImplementedInterfaces": [], - "Members": [ - { - "Kind": "Method", - "Name": "Create", - "Parameters": [ - { - "Name": "policy", - "Type": "Microsoft.Extensions.ObjectPool.IPooledObjectPolicy" - } - ], - "ReturnType": "Microsoft.Extensions.ObjectPool.ObjectPool", - "Virtual": true, - "Abstract": true, - "Visibility": "Public", - "GenericParameter": [ - { - "ParameterName": "T", - "ParameterPosition": 0, - "Class": true, - "BaseTypeOrInterfaces": [] - } - ] - }, - { - "Kind": "Method", - "Name": "Create", - "Parameters": [], - "ReturnType": "Microsoft.Extensions.ObjectPool.ObjectPool", - "Visibility": "Public", - "GenericParameter": [ - { - "ParameterName": "T", - "ParameterPosition": 0, - "New": true, - "Class": true, - "BaseTypeOrInterfaces": [] - } - ] - }, - { - "Kind": "Constructor", - "Name": ".ctor", - "Parameters": [], - "Visibility": "Protected", - "GenericParameter": [] - } - ], - "GenericParameters": [] - }, - { - "Name": "Microsoft.Extensions.ObjectPool.ObjectPoolProviderExtensions", - "Visibility": "Public", - "Kind": "Class", - "Abstract": true, - "Static": true, - "Sealed": true, - "ImplementedInterfaces": [], - "Members": [ - { - "Kind": "Method", - "Name": "CreateStringBuilderPool", - "Parameters": [ - { - "Name": "provider", - "Type": "Microsoft.Extensions.ObjectPool.ObjectPoolProvider" - } - ], - "ReturnType": "Microsoft.Extensions.ObjectPool.ObjectPool", - "Static": true, - "Extension": true, - "Visibility": "Public", - "GenericParameter": [] - }, - { - "Kind": "Method", - "Name": "CreateStringBuilderPool", - "Parameters": [ - { - "Name": "provider", - "Type": "Microsoft.Extensions.ObjectPool.ObjectPoolProvider" - }, - { - "Name": "initialCapacity", - "Type": "System.Int32" - }, - { - "Name": "maximumRetainedCapacity", - "Type": "System.Int32" - } - ], - "ReturnType": "Microsoft.Extensions.ObjectPool.ObjectPool", - "Static": true, - "Extension": true, - "Visibility": "Public", - "GenericParameter": [] - } - ], - "GenericParameters": [] - }, - { - "Name": "Microsoft.Extensions.ObjectPool.PooledObjectPolicy", - "Visibility": "Public", - "Kind": "Class", - "Abstract": true, - "ImplementedInterfaces": [ - "Microsoft.Extensions.ObjectPool.IPooledObjectPolicy" - ], - "Members": [ - { - "Kind": "Method", - "Name": "Create", - "Parameters": [], - "ReturnType": "T0", - "Virtual": true, - "Abstract": true, - "ImplementedInterface": "Microsoft.Extensions.ObjectPool.IPooledObjectPolicy", - "Visibility": "Public", - "GenericParameter": [] - }, - { - "Kind": "Method", - "Name": "Return", - "Parameters": [ - { - "Name": "obj", - "Type": "T0" - } - ], - "ReturnType": "System.Boolean", - "Virtual": true, - "Abstract": true, - "ImplementedInterface": "Microsoft.Extensions.ObjectPool.IPooledObjectPolicy", - "Visibility": "Public", - "GenericParameter": [] - }, - { - "Kind": "Constructor", - "Name": ".ctor", - "Parameters": [], - "Visibility": "Protected", - "GenericParameter": [] - } - ], - "GenericParameters": [ - { - "ParameterName": "T", - "ParameterPosition": 0, - "BaseTypeOrInterfaces": [] - } - ] - }, - { - "Name": "Microsoft.Extensions.ObjectPool.StringBuilderPooledObjectPolicy", - "Visibility": "Public", - "Kind": "Class", - "BaseType": "Microsoft.Extensions.ObjectPool.PooledObjectPolicy", - "ImplementedInterfaces": [], - "Members": [ - { - "Kind": "Method", - "Name": "Create", - "Parameters": [], - "ReturnType": "System.Text.StringBuilder", - "Virtual": true, - "Override": true, - "ImplementedInterface": "Microsoft.Extensions.ObjectPool.IPooledObjectPolicy", - "Visibility": "Public", - "GenericParameter": [] - }, - { - "Kind": "Method", - "Name": "Return", - "Parameters": [ - { - "Name": "obj", - "Type": "System.Text.StringBuilder" - } - ], - "ReturnType": "System.Boolean", - "Virtual": true, - "Override": true, - "ImplementedInterface": "Microsoft.Extensions.ObjectPool.IPooledObjectPolicy", - "Visibility": "Public", - "GenericParameter": [] - }, - { - "Kind": "Method", - "Name": "get_InitialCapacity", - "Parameters": [], - "ReturnType": "System.Int32", - "Visibility": "Public", - "GenericParameter": [] - }, - { - "Kind": "Method", - "Name": "set_InitialCapacity", - "Parameters": [ - { - "Name": "value", - "Type": "System.Int32" - } - ], - "ReturnType": "System.Void", - "Visibility": "Public", - "GenericParameter": [] - }, - { - "Kind": "Method", - "Name": "get_MaximumRetainedCapacity", - "Parameters": [], - "ReturnType": "System.Int32", - "Visibility": "Public", - "GenericParameter": [] - }, - { - "Kind": "Method", - "Name": "set_MaximumRetainedCapacity", - "Parameters": [ - { - "Name": "value", - "Type": "System.Int32" - } - ], - "ReturnType": "System.Void", - "Visibility": "Public", - "GenericParameter": [] - }, - { - "Kind": "Constructor", - "Name": ".ctor", - "Parameters": [], - "Visibility": "Public", - "GenericParameter": [] - } - ], - "GenericParameters": [] - } - ] -} \ No newline at end of file diff --git a/src/Testing/src/Microsoft.AspNetCore.Testing.csproj b/src/Testing/src/Microsoft.AspNetCore.Testing.csproj index 0e33852b62..b3b8046ccb 100644 --- a/src/Testing/src/Microsoft.AspNetCore.Testing.csproj +++ b/src/Testing/src/Microsoft.AspNetCore.Testing.csproj @@ -6,7 +6,6 @@ $(NoWarn);CS1591 true aspnetcore - false true false diff --git a/src/WebEncoders/Directory.Build.props b/src/WebEncoders/Directory.Build.props deleted file mode 100644 index f25c1d90ce..0000000000 --- a/src/WebEncoders/Directory.Build.props +++ /dev/null @@ -1,7 +0,0 @@ - - - - - true - - diff --git a/src/WebEncoders/src/Microsoft.Extensions.WebEncoders.csproj b/src/WebEncoders/src/Microsoft.Extensions.WebEncoders.csproj index bc73592b3f..dfc575c73f 100644 --- a/src/WebEncoders/src/Microsoft.Extensions.WebEncoders.csproj +++ b/src/WebEncoders/src/Microsoft.Extensions.WebEncoders.csproj @@ -7,7 +7,7 @@ true true aspnetcore - true + true diff --git a/src/WebEncoders/src/baseline.netcore.json b/src/WebEncoders/src/baseline.netcore.json deleted file mode 100644 index 6da0ae0754..0000000000 --- a/src/WebEncoders/src/baseline.netcore.json +++ /dev/null @@ -1,564 +0,0 @@ -{ - "AssemblyIdentity": "Microsoft.Extensions.WebEncoders, Version=2.1.1.0, Culture=neutral, PublicKeyToken=adb9793829ddae60", - "Types": [ - { - "Name": "Microsoft.Extensions.WebEncoders.WebEncoderOptions", - "Visibility": "Public", - "Kind": "Class", - "Sealed": true, - "ImplementedInterfaces": [], - "Members": [ - { - "Kind": "Method", - "Name": "get_TextEncoderSettings", - "Parameters": [], - "ReturnType": "System.Text.Encodings.Web.TextEncoderSettings", - "Visibility": "Public", - "GenericParameter": [] - }, - { - "Kind": "Method", - "Name": "set_TextEncoderSettings", - "Parameters": [ - { - "Name": "value", - "Type": "System.Text.Encodings.Web.TextEncoderSettings" - } - ], - "ReturnType": "System.Void", - "Visibility": "Public", - "GenericParameter": [] - }, - { - "Kind": "Constructor", - "Name": ".ctor", - "Parameters": [], - "Visibility": "Public", - "GenericParameter": [] - } - ], - "GenericParameters": [] - }, - { - "Name": "Microsoft.Extensions.WebEncoders.Testing.HtmlTestEncoder", - "Visibility": "Public", - "Kind": "Class", - "Sealed": true, - "BaseType": "System.Text.Encodings.Web.HtmlEncoder", - "ImplementedInterfaces": [], - "Members": [ - { - "Kind": "Method", - "Name": "get_MaxOutputCharactersPerInputCharacter", - "Parameters": [], - "ReturnType": "System.Int32", - "Virtual": true, - "Override": true, - "Visibility": "Public", - "GenericParameter": [] - }, - { - "Kind": "Method", - "Name": "Encode", - "Parameters": [ - { - "Name": "value", - "Type": "System.String" - } - ], - "ReturnType": "System.String", - "Virtual": true, - "Override": true, - "Visibility": "Public", - "GenericParameter": [] - }, - { - "Kind": "Method", - "Name": "Encode", - "Parameters": [ - { - "Name": "output", - "Type": "System.IO.TextWriter" - }, - { - "Name": "value", - "Type": "System.Char[]" - }, - { - "Name": "startIndex", - "Type": "System.Int32" - }, - { - "Name": "characterCount", - "Type": "System.Int32" - } - ], - "ReturnType": "System.Void", - "Virtual": true, - "Override": true, - "Visibility": "Public", - "GenericParameter": [] - }, - { - "Kind": "Method", - "Name": "Encode", - "Parameters": [ - { - "Name": "output", - "Type": "System.IO.TextWriter" - }, - { - "Name": "value", - "Type": "System.String" - }, - { - "Name": "startIndex", - "Type": "System.Int32" - }, - { - "Name": "characterCount", - "Type": "System.Int32" - } - ], - "ReturnType": "System.Void", - "Virtual": true, - "Override": true, - "Visibility": "Public", - "GenericParameter": [] - }, - { - "Kind": "Method", - "Name": "WillEncode", - "Parameters": [ - { - "Name": "unicodeScalar", - "Type": "System.Int32" - } - ], - "ReturnType": "System.Boolean", - "Virtual": true, - "Override": true, - "Visibility": "Public", - "GenericParameter": [] - }, - { - "Kind": "Method", - "Name": "FindFirstCharacterToEncode", - "Parameters": [ - { - "Name": "text", - "Type": "System.Char*" - }, - { - "Name": "textLength", - "Type": "System.Int32" - } - ], - "ReturnType": "System.Int32", - "Virtual": true, - "Override": true, - "Visibility": "Public", - "GenericParameter": [] - }, - { - "Kind": "Method", - "Name": "TryEncodeUnicodeScalar", - "Parameters": [ - { - "Name": "unicodeScalar", - "Type": "System.Int32" - }, - { - "Name": "buffer", - "Type": "System.Char*" - }, - { - "Name": "bufferLength", - "Type": "System.Int32" - }, - { - "Name": "numberOfCharactersWritten", - "Type": "System.Int32", - "Direction": "Out" - } - ], - "ReturnType": "System.Boolean", - "Virtual": true, - "Override": true, - "Visibility": "Public", - "GenericParameter": [] - }, - { - "Kind": "Constructor", - "Name": ".ctor", - "Parameters": [], - "Visibility": "Public", - "GenericParameter": [] - } - ], - "GenericParameters": [] - }, - { - "Name": "Microsoft.Extensions.WebEncoders.Testing.JavaScriptTestEncoder", - "Visibility": "Public", - "Kind": "Class", - "BaseType": "System.Text.Encodings.Web.JavaScriptEncoder", - "ImplementedInterfaces": [], - "Members": [ - { - "Kind": "Method", - "Name": "get_MaxOutputCharactersPerInputCharacter", - "Parameters": [], - "ReturnType": "System.Int32", - "Virtual": true, - "Override": true, - "Visibility": "Public", - "GenericParameter": [] - }, - { - "Kind": "Method", - "Name": "Encode", - "Parameters": [ - { - "Name": "value", - "Type": "System.String" - } - ], - "ReturnType": "System.String", - "Virtual": true, - "Override": true, - "Visibility": "Public", - "GenericParameter": [] - }, - { - "Kind": "Method", - "Name": "Encode", - "Parameters": [ - { - "Name": "output", - "Type": "System.IO.TextWriter" - }, - { - "Name": "value", - "Type": "System.Char[]" - }, - { - "Name": "startIndex", - "Type": "System.Int32" - }, - { - "Name": "characterCount", - "Type": "System.Int32" - } - ], - "ReturnType": "System.Void", - "Virtual": true, - "Override": true, - "Visibility": "Public", - "GenericParameter": [] - }, - { - "Kind": "Method", - "Name": "Encode", - "Parameters": [ - { - "Name": "output", - "Type": "System.IO.TextWriter" - }, - { - "Name": "value", - "Type": "System.String" - }, - { - "Name": "startIndex", - "Type": "System.Int32" - }, - { - "Name": "characterCount", - "Type": "System.Int32" - } - ], - "ReturnType": "System.Void", - "Virtual": true, - "Override": true, - "Visibility": "Public", - "GenericParameter": [] - }, - { - "Kind": "Method", - "Name": "WillEncode", - "Parameters": [ - { - "Name": "unicodeScalar", - "Type": "System.Int32" - } - ], - "ReturnType": "System.Boolean", - "Virtual": true, - "Override": true, - "Visibility": "Public", - "GenericParameter": [] - }, - { - "Kind": "Method", - "Name": "FindFirstCharacterToEncode", - "Parameters": [ - { - "Name": "text", - "Type": "System.Char*" - }, - { - "Name": "textLength", - "Type": "System.Int32" - } - ], - "ReturnType": "System.Int32", - "Virtual": true, - "Override": true, - "Visibility": "Public", - "GenericParameter": [] - }, - { - "Kind": "Method", - "Name": "TryEncodeUnicodeScalar", - "Parameters": [ - { - "Name": "unicodeScalar", - "Type": "System.Int32" - }, - { - "Name": "buffer", - "Type": "System.Char*" - }, - { - "Name": "bufferLength", - "Type": "System.Int32" - }, - { - "Name": "numberOfCharactersWritten", - "Type": "System.Int32", - "Direction": "Out" - } - ], - "ReturnType": "System.Boolean", - "Virtual": true, - "Override": true, - "Visibility": "Public", - "GenericParameter": [] - }, - { - "Kind": "Constructor", - "Name": ".ctor", - "Parameters": [], - "Visibility": "Public", - "GenericParameter": [] - } - ], - "GenericParameters": [] - }, - { - "Name": "Microsoft.Extensions.WebEncoders.Testing.UrlTestEncoder", - "Visibility": "Public", - "Kind": "Class", - "BaseType": "System.Text.Encodings.Web.UrlEncoder", - "ImplementedInterfaces": [], - "Members": [ - { - "Kind": "Method", - "Name": "get_MaxOutputCharactersPerInputCharacter", - "Parameters": [], - "ReturnType": "System.Int32", - "Virtual": true, - "Override": true, - "Visibility": "Public", - "GenericParameter": [] - }, - { - "Kind": "Method", - "Name": "Encode", - "Parameters": [ - { - "Name": "value", - "Type": "System.String" - } - ], - "ReturnType": "System.String", - "Virtual": true, - "Override": true, - "Visibility": "Public", - "GenericParameter": [] - }, - { - "Kind": "Method", - "Name": "Encode", - "Parameters": [ - { - "Name": "output", - "Type": "System.IO.TextWriter" - }, - { - "Name": "value", - "Type": "System.Char[]" - }, - { - "Name": "startIndex", - "Type": "System.Int32" - }, - { - "Name": "characterCount", - "Type": "System.Int32" - } - ], - "ReturnType": "System.Void", - "Virtual": true, - "Override": true, - "Visibility": "Public", - "GenericParameter": [] - }, - { - "Kind": "Method", - "Name": "Encode", - "Parameters": [ - { - "Name": "output", - "Type": "System.IO.TextWriter" - }, - { - "Name": "value", - "Type": "System.String" - }, - { - "Name": "startIndex", - "Type": "System.Int32" - }, - { - "Name": "characterCount", - "Type": "System.Int32" - } - ], - "ReturnType": "System.Void", - "Virtual": true, - "Override": true, - "Visibility": "Public", - "GenericParameter": [] - }, - { - "Kind": "Method", - "Name": "WillEncode", - "Parameters": [ - { - "Name": "unicodeScalar", - "Type": "System.Int32" - } - ], - "ReturnType": "System.Boolean", - "Virtual": true, - "Override": true, - "Visibility": "Public", - "GenericParameter": [] - }, - { - "Kind": "Method", - "Name": "FindFirstCharacterToEncode", - "Parameters": [ - { - "Name": "text", - "Type": "System.Char*" - }, - { - "Name": "textLength", - "Type": "System.Int32" - } - ], - "ReturnType": "System.Int32", - "Virtual": true, - "Override": true, - "Visibility": "Public", - "GenericParameter": [] - }, - { - "Kind": "Method", - "Name": "TryEncodeUnicodeScalar", - "Parameters": [ - { - "Name": "unicodeScalar", - "Type": "System.Int32" - }, - { - "Name": "buffer", - "Type": "System.Char*" - }, - { - "Name": "bufferLength", - "Type": "System.Int32" - }, - { - "Name": "numberOfCharactersWritten", - "Type": "System.Int32", - "Direction": "Out" - } - ], - "ReturnType": "System.Boolean", - "Virtual": true, - "Override": true, - "Visibility": "Public", - "GenericParameter": [] - }, - { - "Kind": "Constructor", - "Name": ".ctor", - "Parameters": [], - "Visibility": "Public", - "GenericParameter": [] - } - ], - "GenericParameters": [] - }, - { - "Name": "Microsoft.Extensions.DependencyInjection.EncoderServiceCollectionExtensions", - "Visibility": "Public", - "Kind": "Class", - "Abstract": true, - "Static": true, - "Sealed": true, - "ImplementedInterfaces": [], - "Members": [ - { - "Kind": "Method", - "Name": "AddWebEncoders", - "Parameters": [ - { - "Name": "services", - "Type": "Microsoft.Extensions.DependencyInjection.IServiceCollection" - } - ], - "ReturnType": "Microsoft.Extensions.DependencyInjection.IServiceCollection", - "Static": true, - "Extension": true, - "Visibility": "Public", - "GenericParameter": [] - }, - { - "Kind": "Method", - "Name": "AddWebEncoders", - "Parameters": [ - { - "Name": "services", - "Type": "Microsoft.Extensions.DependencyInjection.IServiceCollection" - }, - { - "Name": "setupAction", - "Type": "System.Action" - } - ], - "ReturnType": "Microsoft.Extensions.DependencyInjection.IServiceCollection", - "Static": true, - "Extension": true, - "Visibility": "Public", - "GenericParameter": [] - } - ], - "GenericParameters": [] - } - ] -} \ No newline at end of file From c43d3b823e37de499c3113351575ed47366638d6 Mon Sep 17 00:00:00 2001 From: Nate McMaster Date: Tue, 29 Jan 2019 18:34:54 -0800 Subject: [PATCH 0051/1101] Cleanup conversion to Arcade (#1014) * Remove obsolete targets, properties, and scripts * Replace IsProductComponent with IsShipping * Undo bad merge to version.props * Update documentation, and put workarounds into a common file * Replace usages of RepositoryRoot with RepoRoot * Remove API baselines * Remove unnecessary restore feeds and split workarounds into two files * Enable PR checks on all branches, and disable autocancel --- .../src/build/Microsoft.Extensions.Logging.Testing.props | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/Testing/src/build/Microsoft.Extensions.Logging.Testing.props b/src/Testing/src/build/Microsoft.Extensions.Logging.Testing.props index 0d2585146c..c503c32d40 100644 --- a/src/Testing/src/build/Microsoft.Extensions.Logging.Testing.props +++ b/src/Testing/src/build/Microsoft.Extensions.Logging.Testing.props @@ -1,8 +1,9 @@  + $(RepositoryRoot) $(ASPNETCORE_TEST_LOG_DIR) - $(RepositoryRoot)artifacts\logs\ + $(RepoRoot)artifacts\log\ - \ No newline at end of file + From 070bba8ad4dfae6dc0a0e1df438592960365c35e Mon Sep 17 00:00:00 2001 From: Kahbazi Date: Wed, 9 Jan 2019 08:16:28 +0330 Subject: [PATCH 0052/1101] Add event name in localization\n\nCommit migrated from https://github.com/dotnet/extensions/commit/d2b1ad3d233a0750647ed2c73343a293f08c5709 --- .../Internal/ResourceManagerStringLocalizerLoggerExtensions.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Localization/Localization/src/Internal/ResourceManagerStringLocalizerLoggerExtensions.cs b/src/Localization/Localization/src/Internal/ResourceManagerStringLocalizerLoggerExtensions.cs index 456e07009e..63f40536ca 100644 --- a/src/Localization/Localization/src/Internal/ResourceManagerStringLocalizerLoggerExtensions.cs +++ b/src/Localization/Localization/src/Internal/ResourceManagerStringLocalizerLoggerExtensions.cs @@ -15,7 +15,7 @@ namespace Microsoft.Extensions.Localization.Internal { _searchedLocation = LoggerMessage.Define( LogLevel.Debug, - 1, + new EventId(1, "SearchedLocation"), $"{nameof(ResourceManagerStringLocalizer)} searched for '{{Key}}' in '{{LocationSearched}}' with culture '{{Culture}}'."); } From bbca5f30e729c957e3a89fd717a014c18fd5cc1f Mon Sep 17 00:00:00 2001 From: Nate McMaster Date: Thu, 31 Jan 2019 13:20:34 -0800 Subject: [PATCH 0053/1101] Remove implicit references for non-test projects (dotnet/extensions#1037) \n\nCommit migrated from https://github.com/dotnet/extensions/commit/e504b4ee74bba3a7df2be5612e8d509ad61b0c24 --- src/Testing/src/Microsoft.AspNetCore.Testing.csproj | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/src/Testing/src/Microsoft.AspNetCore.Testing.csproj b/src/Testing/src/Microsoft.AspNetCore.Testing.csproj index b3b8046ccb..9da267c419 100644 --- a/src/Testing/src/Microsoft.AspNetCore.Testing.csproj +++ b/src/Testing/src/Microsoft.AspNetCore.Testing.csproj @@ -6,6 +6,8 @@ $(NoWarn);CS1591 true aspnetcore + + false true false @@ -19,6 +21,15 @@ + + From a663bcf605510df7c7114fccaf9943e4595a2b00 Mon Sep 17 00:00:00 2001 From: Nate McMaster Date: Fri, 1 Feb 2019 10:33:04 -0800 Subject: [PATCH 0054/1101] Add code check CI tests, add docs about ReferenceResolution, and other cleanups (dotnet/extensions#1044) Changes: * Add a step which checks that generated code is up to date * Copy over the documentation on how to work with `` * Fixup a broken reference in the .sln * Fix the casing on Microsoft.Extensions.ValueStopwatch.Sources * Remove some unused references and variables\n\nCommit migrated from https://github.com/dotnet/extensions/commit/92acd8b95901f1e8f47d3d128cb5f7c38b684644 --- ...rosoft.Extensions.FileProviders.Embedded.Manifest.Task.csproj | 1 - ....Extensions.FileProviders.Embedded.Manifest.Task.Tests.csproj | 1 - 2 files changed, 2 deletions(-) diff --git a/src/FileProviders/Manifest.MSBuildTask/src/Microsoft.Extensions.FileProviders.Embedded.Manifest.Task.csproj b/src/FileProviders/Manifest.MSBuildTask/src/Microsoft.Extensions.FileProviders.Embedded.Manifest.Task.csproj index f8ae098eab..cdc4ffdcb0 100644 --- a/src/FileProviders/Manifest.MSBuildTask/src/Microsoft.Extensions.FileProviders.Embedded.Manifest.Task.csproj +++ b/src/FileProviders/Manifest.MSBuildTask/src/Microsoft.Extensions.FileProviders.Embedded.Manifest.Task.csproj @@ -11,7 +11,6 @@ - diff --git a/src/FileProviders/Manifest.MSBuildTask/test/Microsoft.Extensions.FileProviders.Embedded.Manifest.Task.Tests.csproj b/src/FileProviders/Manifest.MSBuildTask/test/Microsoft.Extensions.FileProviders.Embedded.Manifest.Task.Tests.csproj index 3f7647a4f1..ed68958fe8 100644 --- a/src/FileProviders/Manifest.MSBuildTask/test/Microsoft.Extensions.FileProviders.Embedded.Manifest.Task.Tests.csproj +++ b/src/FileProviders/Manifest.MSBuildTask/test/Microsoft.Extensions.FileProviders.Embedded.Manifest.Task.Tests.csproj @@ -9,7 +9,6 @@ - From b47bd1789403016d7a778a78402ae4e330e7da60 Mon Sep 17 00:00:00 2001 From: Pranav K Date: Fri, 1 Feb 2019 14:12:49 -0800 Subject: [PATCH 0055/1101] Publish Microsoft.Interop.Js and Mono.WebAssembly.Interop (dotnet/extensions#1048) * Publish Microsoft.Interop.Js and Mono.WebAssembly.Interop \n\nCommit migrated from https://github.com/dotnet/extensions/commit/bc2e7150f00198a160cb5b0452a294f30b78af76 --- .../src/Microsoft.JSInterop.JS.csproj | 13 - .../src/Microsoft.JSInterop.JS.npmproj | 12 + .../src/package-lock.json | 269 +++++++++++++++++- .../Microsoft.JSInterop.JS/src/package.json | 30 +- .../Microsoft.JSInterop.JS/src/tslint.json | 14 + .../src/Mono.WebAssembly.Interop.csproj | 5 +- 6 files changed, 304 insertions(+), 39 deletions(-) delete mode 100644 src/JSInterop/Microsoft.JSInterop.JS/src/Microsoft.JSInterop.JS.csproj create mode 100644 src/JSInterop/Microsoft.JSInterop.JS/src/Microsoft.JSInterop.JS.npmproj create mode 100644 src/JSInterop/Microsoft.JSInterop.JS/src/tslint.json diff --git a/src/JSInterop/Microsoft.JSInterop.JS/src/Microsoft.JSInterop.JS.csproj b/src/JSInterop/Microsoft.JSInterop.JS/src/Microsoft.JSInterop.JS.csproj deleted file mode 100644 index 7f806d0afa..0000000000 --- a/src/JSInterop/Microsoft.JSInterop.JS/src/Microsoft.JSInterop.JS.csproj +++ /dev/null @@ -1,13 +0,0 @@ - - - - netstandard2.0 - Latest - false - - - - - - - diff --git a/src/JSInterop/Microsoft.JSInterop.JS/src/Microsoft.JSInterop.JS.npmproj b/src/JSInterop/Microsoft.JSInterop.JS/src/Microsoft.JSInterop.JS.npmproj new file mode 100644 index 0000000000..e8d0554ff7 --- /dev/null +++ b/src/JSInterop/Microsoft.JSInterop.JS/src/Microsoft.JSInterop.JS.npmproj @@ -0,0 +1,12 @@ + + + + + @dotnet/jsinterop + true + false + true + + + + diff --git a/src/JSInterop/Microsoft.JSInterop.JS/src/package-lock.json b/src/JSInterop/Microsoft.JSInterop.JS/src/package-lock.json index a7f31ead4f..4c82321255 100644 --- a/src/JSInterop/Microsoft.JSInterop.JS/src/package-lock.json +++ b/src/JSInterop/Microsoft.JSInterop.JS/src/package-lock.json @@ -1,9 +1,56 @@ { "name": "@dotnet/jsinterop", - "version": "0.1.1", + "version": "3.0.0-dev", "lockfileVersion": 1, "requires": true, "dependencies": { + "ansi-regex": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", + "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=", + "dev": true + }, + "ansi-styles": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz", + "integrity": "sha1-tDLdM1i2NM914eRmQ2gkBTPB3b4=", + "dev": true + }, + "argparse": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", + "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", + "dev": true, + "requires": { + "sprintf-js": "~1.0.2" + } + }, + "babel-code-frame": { + "version": "6.26.0", + "resolved": "https://registry.npmjs.org/babel-code-frame/-/babel-code-frame-6.26.0.tgz", + "integrity": "sha1-Y/1D99weO7fONZR9uP42mj9Yx0s=", + "dev": true, + "requires": { + "chalk": "^1.1.3", + "esutils": "^2.0.2", + "js-tokens": "^3.0.2" + }, + "dependencies": { + "chalk": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", + "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=", + "dev": true, + "requires": { + "ansi-styles": "^2.2.1", + "escape-string-regexp": "^1.0.2", + "has-ansi": "^2.0.0", + "strip-ansi": "^3.0.0", + "supports-color": "^2.0.0" + } + } + } + }, "balanced-match": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", @@ -16,16 +63,98 @@ "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", "dev": true, "requires": { - "balanced-match": "1.0.0", + "balanced-match": "^1.0.0", "concat-map": "0.0.1" } }, + "builtin-modules": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/builtin-modules/-/builtin-modules-1.1.1.tgz", + "integrity": "sha1-Jw8HbFpywC9bZaR9+Uxf46J4iS8=", + "dev": true + }, + "chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "dev": true, + "requires": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + }, + "dependencies": { + "ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "dev": true, + "requires": { + "color-convert": "^1.9.0" + } + }, + "supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "requires": { + "has-flag": "^3.0.0" + } + } + } + }, + "color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "dev": true, + "requires": { + "color-name": "1.1.3" + } + }, + "color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=", + "dev": true + }, + "commander": { + "version": "2.19.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.19.0.tgz", + "integrity": "sha512-6tvAOO+D6OENvRAh524Dh9jcfKTYDQAqvqezbCW82xj5X0pSrcpxtvRKHLG0yBY6SD7PSDrJaj+0AiOcKVd1Xg==", + "dev": true + }, "concat-map": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=", "dev": true }, + "diff": { + "version": "3.5.0", + "resolved": "https://registry.npmjs.org/diff/-/diff-3.5.0.tgz", + "integrity": "sha512-A46qtFgd+g7pDZinpnwiRJtxbC1hpgf0uzP3iG89scHk0AUC7A1TGxf5OiiOUv/JMZR8GOt8hL900hV0bOy5xA==", + "dev": true + }, + "escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=", + "dev": true + }, + "esprima": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", + "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", + "dev": true + }, + "esutils": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.2.tgz", + "integrity": "sha1-Cr9PHKpbyx96nYrMbepPqqBLrJs=", + "dev": true + }, "fs.realpath": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", @@ -38,22 +167,37 @@ "integrity": "sha512-vcfuiIxogLV4DlGBHIUOwI0IbrJ8HWPc4MU7HzviGeNho/UJDfi6B5p3sHeWIQ0KGIU0Jpxi5ZHxemQfLkkAwQ==", "dev": true, "requires": { - "fs.realpath": "1.0.0", - "inflight": "1.0.6", - "inherits": "2.0.3", - "minimatch": "3.0.4", - "once": "1.4.0", - "path-is-absolute": "1.0.1" + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.0.4", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" } }, + "has-ansi": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/has-ansi/-/has-ansi-2.0.0.tgz", + "integrity": "sha1-NPUEnOHs3ysGSa8+8k5F7TVBbZE=", + "dev": true, + "requires": { + "ansi-regex": "^2.0.0" + } + }, + "has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", + "dev": true + }, "inflight": { "version": "1.0.6", "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", "dev": true, "requires": { - "once": "1.4.0", - "wrappy": "1.0.2" + "once": "^1.3.0", + "wrappy": "1" } }, "inherits": { @@ -62,13 +206,29 @@ "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=", "dev": true }, + "js-tokens": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-3.0.2.tgz", + "integrity": "sha1-mGbfOVECEw449/mWvOtlRDIJwls=", + "dev": true + }, + "js-yaml": { + "version": "3.12.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.12.1.tgz", + "integrity": "sha512-um46hB9wNOKlwkHgiuyEVAybXBjwFUV0Z/RaHJblRd9DXltue9FTYvzCr9ErQrK9Adz5MU4gHWVaNUfdmrC8qA==", + "dev": true, + "requires": { + "argparse": "^1.0.7", + "esprima": "^4.0.0" + } + }, "minimatch": { "version": "3.0.4", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", "dev": true, "requires": { - "brace-expansion": "1.1.11" + "brace-expansion": "^1.1.7" } }, "once": { @@ -77,7 +237,7 @@ "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", "dev": true, "requires": { - "wrappy": "1.0.2" + "wrappy": "1" } }, "path-is-absolute": { @@ -86,15 +246,98 @@ "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=", "dev": true }, + "path-parse": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.6.tgz", + "integrity": "sha512-GSmOT2EbHrINBf9SR7CDELwlJ8AENk3Qn7OikK4nFYAu3Ote2+JYNVvkpAEQm3/TLNEJFD/xZJjzyxg3KBWOzw==", + "dev": true + }, + "resolve": { + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.10.0.tgz", + "integrity": "sha512-3sUr9aq5OfSg2S9pNtPA9hL1FVEAjvfOC4leW0SNf/mpnaakz2a9femSd6LqAww2RaFctwyf1lCqnTHuF1rxDg==", + "dev": true, + "requires": { + "path-parse": "^1.0.6" + } + }, "rimraf": { "version": "2.6.2", "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.6.2.tgz", "integrity": "sha512-lreewLK/BlghmxtfH36YYVg1i8IAce4TI7oao75I1g245+6BctqTVQiBP3YUJ9C6DQOXJmkYR9X9fCLtCOJc5w==", "dev": true, "requires": { - "glob": "7.1.3" + "glob": "^7.0.5" } }, + "semver": { + "version": "5.6.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.6.0.tgz", + "integrity": "sha512-RS9R6R35NYgQn++fkDWaOmqGoj4Ek9gGs+DPxNUZKuwE183xjJroKvyo1IzVFeXvUrvmALy6FWD5xrdJT25gMg==", + "dev": true + }, + "sprintf-js": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", + "integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=", + "dev": true + }, + "strip-ansi": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", + "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", + "dev": true, + "requires": { + "ansi-regex": "^2.0.0" + } + }, + "supports-color": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz", + "integrity": "sha1-U10EXOa2Nj+kARcIRimZXp3zJMc=", + "dev": true + }, + "tslib": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.9.3.tgz", + "integrity": "sha512-4krF8scpejhaOgqzBEcGM7yDIEfi0/8+8zDRZhNZZ2kjmHJ4hv3zCbQWxoJGz1iw5U0Jl0nma13xzHXcncMavQ==", + "dev": true + }, + "tslint": { + "version": "5.12.1", + "resolved": "https://registry.npmjs.org/tslint/-/tslint-5.12.1.tgz", + "integrity": "sha512-sfodBHOucFg6egff8d1BvuofoOQ/nOeYNfbp7LDlKBcLNrL3lmS5zoiDGyOMdT7YsEXAwWpTdAHwOGOc8eRZAw==", + "dev": true, + "requires": { + "babel-code-frame": "^6.22.0", + "builtin-modules": "^1.1.1", + "chalk": "^2.3.0", + "commander": "^2.12.1", + "diff": "^3.2.0", + "glob": "^7.1.1", + "js-yaml": "^3.7.0", + "minimatch": "^3.0.4", + "resolve": "^1.3.2", + "semver": "^5.3.0", + "tslib": "^1.8.0", + "tsutils": "^2.27.2" + } + }, + "tsutils": { + "version": "2.29.0", + "resolved": "https://registry.npmjs.org/tsutils/-/tsutils-2.29.0.tgz", + "integrity": "sha512-g5JVHCIJwzfISaXpXE1qvNalca5Jwob6FjI4AoPlqMusJ6ftFE7IkkFoMhVLRgK+4Kx3gkzb8UZK5t5yTTvEmA==", + "dev": true, + "requires": { + "tslib": "^1.8.1" + } + }, + "typescript": { + "version": "2.9.2", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-2.9.2.tgz", + "integrity": "sha512-Gr4p6nFNaoufRIY4NMdpQRNmgxVIGMs4Fcu/ujdYk3nAZqk7supzBE9idmvfZIlH/Cuj//dvi+019qEue9lV0w==", + "dev": true + }, "wrappy": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", diff --git a/src/JSInterop/Microsoft.JSInterop.JS/src/package.json b/src/JSInterop/Microsoft.JSInterop.JS/src/package.json index a84734bc46..8be950e16f 100644 --- a/src/JSInterop/Microsoft.JSInterop.JS/src/package.json +++ b/src/JSInterop/Microsoft.JSInterop.JS/src/package.json @@ -1,25 +1,31 @@ { "name": "@dotnet/jsinterop", - "version": "0.1.1", + "version": "3.0.0-dev", "description": "Provides abstractions and features for interop between .NET and JavaScript code.", "main": "dist/Microsoft.JSInterop.js", "types": "dist/Microsoft.JSInterop.d.js", "scripts": { - "prepublish": "rimraf dist && dotnet build && echo 'Finished building NPM package \"@dotnet/jsinterop\"'" - }, - "files": [ - "dist/**" - ], - "author": "Microsoft", - "license": "Apache-2.0", - "bugs": { - "url": "https://github.com/dotnet/jsinterop/issues" + "clean": "node node_modules/rimraf/bin.js ./dist", + "build": "npm run clean && npm run build:esm", + "build:lint": "node node_modules/tslint/bin/tslint -p ./tsconfig.json", + "build:esm": "node node_modules/typescript/bin/tsc --project ./tsconfig.json" }, "repository": { "type": "git", - "url": "https://github.com/dotnet/jsinterop.git" + "url": "git+https://github.com/aspnet/AspNetCore.git" }, + "author": "Microsoft", + "license": "Apache-2.0", + "bugs": { + "url": "https://github.com/aspnet/AspNetCore/issues" + }, + "homepage": "https://github.com/aspnet/Extensions/tree/master/src/JSInterop#readme", + "files": [ + "dist/**" + ], "devDependencies": { - "rimraf": "^2.5.4" + "rimraf": "^2.5.4", + "tslint": "^5.9.1", + "typescript": "^2.7.1" } } diff --git a/src/JSInterop/Microsoft.JSInterop.JS/src/tslint.json b/src/JSInterop/Microsoft.JSInterop.JS/src/tslint.json new file mode 100644 index 0000000000..5c38bef990 --- /dev/null +++ b/src/JSInterop/Microsoft.JSInterop.JS/src/tslint.json @@ -0,0 +1,14 @@ +{ + "extends": "tslint:recommended", + "rules": { + "max-line-length": { "options": [300] }, + "member-ordering": false, + "interface-name": false, + "unified-signatures": false, + "max-classes-per-file": false, + "no-floating-promises": true, + "no-empty": false, + "no-bitwise": false, + "no-console": false + } +} diff --git a/src/JSInterop/Mono.WebAssembly.Interop/src/Mono.WebAssembly.Interop.csproj b/src/JSInterop/Mono.WebAssembly.Interop/src/Mono.WebAssembly.Interop.csproj index 0ad8effd80..413d084e48 100644 --- a/src/JSInterop/Mono.WebAssembly.Interop/src/Mono.WebAssembly.Interop.csproj +++ b/src/JSInterop/Mono.WebAssembly.Interop/src/Mono.WebAssembly.Interop.csproj @@ -2,7 +2,10 @@ netstandard2.0 - false + Abstractions and features for interop between Mono WebAssembly and JavaScript code. + wasm;javascript;interop + true + true From 2c0287d686dc7becb6aa20d097d78505e0280f8d Mon Sep 17 00:00:00 2001 From: marciomyst Date: Fri, 8 Feb 2019 15:35:13 -0200 Subject: [PATCH 0056/1101] Fix ignored exception parameter on Degraded method The 'exception' parameter was ignored and a hardcoded null value was used on HealthCheckResult.Degraded\n\nCommit migrated from https://github.com/dotnet/extensions/commit/dbfc2dee3a582b581f91386cff9a356fb111ca37 --- src/HealthChecks/Abstractions/src/HealthCheckResult.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/HealthChecks/Abstractions/src/HealthCheckResult.cs b/src/HealthChecks/Abstractions/src/HealthCheckResult.cs index e01cb5aceb..7f4522da19 100644 --- a/src/HealthChecks/Abstractions/src/HealthCheckResult.cs +++ b/src/HealthChecks/Abstractions/src/HealthCheckResult.cs @@ -70,7 +70,7 @@ namespace Microsoft.Extensions.Diagnostics.HealthChecks /// A representing a degraged component. public static HealthCheckResult Degraded(string description = null, Exception exception = null, IReadOnlyDictionary data = null) { - return new HealthCheckResult(status: HealthStatus.Degraded, description, exception: null, data); + return new HealthCheckResult(status: HealthStatus.Degraded, description, exception: exception, data); } /// From 39b6a19f90b13581717a662654dc5d5ded7bfa06 Mon Sep 17 00:00:00 2001 From: Pavel Krymets Date: Mon, 11 Feb 2019 12:10:15 -0800 Subject: [PATCH 0057/1101] Add reference assembly generations support (dotnet/extensions#1093) \n\nCommit migrated from https://github.com/dotnet/extensions/commit/8ad1395c9181c0f370ac8012760984541f557eee --- ...Extensions.Configuration.KeyPerFile.csproj | 12 +++ ...Configuration.KeyPerFile.netstandard2.0.cs | 29 ++++++ ...t.Extensions.FileProviders.Embedded.csproj | 11 +++ ...s.FileProviders.Embedded.netstandard2.0.cs | 39 ++++++++ ...agnostics.HealthChecks.Abstractions.csproj | 11 +++ ...ealthChecks.Abstractions.netstandard2.0.cs | 67 ++++++++++++++ ...Extensions.Diagnostics.HealthChecks.csproj | 13 +++ ...Diagnostics.HealthChecks.netstandard2.0.cs | 52 +++++++++++ .../ref/Microsoft.JSInterop.csproj | 11 +++ .../ref/Microsoft.JSInterop.netstandard2.0.cs | 76 ++++++++++++++++ .../ref/Mono.WebAssembly.Interop.csproj | 11 +++ ...Mono.WebAssembly.Interop.netstandard2.0.cs | 16 ++++ ...xtensions.Localization.Abstractions.csproj | 11 +++ ...ocalization.Abstractions.netstandard2.0.cs | 47 ++++++++++ .../Microsoft.Extensions.Localization.csproj | 14 +++ ....Extensions.Localization.netstandard2.0.cs | 91 +++++++++++++++++++ .../Microsoft.Extensions.ObjectPool.csproj | 11 +++ ...ft.Extensions.ObjectPool.netstandard2.0.cs | 72 +++++++++++++++ src/WebEncoders/Directory.Build.props | 8 ++ .../Microsoft.Extensions.WebEncoders.csproj | 13 +++ ...t.Extensions.WebEncoders.netstandard2.0.cs | 55 +++++++++++ .../Microsoft.Extensions.WebEncoders.csproj | 1 - 22 files changed, 670 insertions(+), 1 deletion(-) create mode 100644 src/Configuration.KeyPerFile/ref/Microsoft.Extensions.Configuration.KeyPerFile.csproj create mode 100644 src/Configuration.KeyPerFile/ref/Microsoft.Extensions.Configuration.KeyPerFile.netstandard2.0.cs create mode 100644 src/FileProviders/Embedded/ref/Microsoft.Extensions.FileProviders.Embedded.csproj create mode 100644 src/FileProviders/Embedded/ref/Microsoft.Extensions.FileProviders.Embedded.netstandard2.0.cs create mode 100644 src/HealthChecks/Abstractions/ref/Microsoft.Extensions.Diagnostics.HealthChecks.Abstractions.csproj create mode 100644 src/HealthChecks/Abstractions/ref/Microsoft.Extensions.Diagnostics.HealthChecks.Abstractions.netstandard2.0.cs create mode 100644 src/HealthChecks/HealthChecks/ref/Microsoft.Extensions.Diagnostics.HealthChecks.csproj create mode 100644 src/HealthChecks/HealthChecks/ref/Microsoft.Extensions.Diagnostics.HealthChecks.netstandard2.0.cs create mode 100644 src/JSInterop/Microsoft.JSInterop/ref/Microsoft.JSInterop.csproj create mode 100644 src/JSInterop/Microsoft.JSInterop/ref/Microsoft.JSInterop.netstandard2.0.cs create mode 100644 src/JSInterop/Mono.WebAssembly.Interop/ref/Mono.WebAssembly.Interop.csproj create mode 100644 src/JSInterop/Mono.WebAssembly.Interop/ref/Mono.WebAssembly.Interop.netstandard2.0.cs create mode 100644 src/Localization/Abstractions/ref/Microsoft.Extensions.Localization.Abstractions.csproj create mode 100644 src/Localization/Abstractions/ref/Microsoft.Extensions.Localization.Abstractions.netstandard2.0.cs create mode 100644 src/Localization/Localization/ref/Microsoft.Extensions.Localization.csproj create mode 100644 src/Localization/Localization/ref/Microsoft.Extensions.Localization.netstandard2.0.cs create mode 100644 src/ObjectPool/ref/Microsoft.Extensions.ObjectPool.csproj create mode 100644 src/ObjectPool/ref/Microsoft.Extensions.ObjectPool.netstandard2.0.cs create mode 100644 src/WebEncoders/Directory.Build.props create mode 100644 src/WebEncoders/ref/Microsoft.Extensions.WebEncoders.csproj create mode 100644 src/WebEncoders/ref/Microsoft.Extensions.WebEncoders.netstandard2.0.cs diff --git a/src/Configuration.KeyPerFile/ref/Microsoft.Extensions.Configuration.KeyPerFile.csproj b/src/Configuration.KeyPerFile/ref/Microsoft.Extensions.Configuration.KeyPerFile.csproj new file mode 100644 index 0000000000..3df73aba0c --- /dev/null +++ b/src/Configuration.KeyPerFile/ref/Microsoft.Extensions.Configuration.KeyPerFile.csproj @@ -0,0 +1,12 @@ + + + + netstandard2.0 + false + + + + + + + diff --git a/src/Configuration.KeyPerFile/ref/Microsoft.Extensions.Configuration.KeyPerFile.netstandard2.0.cs b/src/Configuration.KeyPerFile/ref/Microsoft.Extensions.Configuration.KeyPerFile.netstandard2.0.cs new file mode 100644 index 0000000000..e26ca1909d --- /dev/null +++ b/src/Configuration.KeyPerFile/ref/Microsoft.Extensions.Configuration.KeyPerFile.netstandard2.0.cs @@ -0,0 +1,29 @@ +// 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. + +namespace Microsoft.Extensions.Configuration +{ + public static partial class KeyPerFileConfigurationBuilderExtensions + { + public static Microsoft.Extensions.Configuration.IConfigurationBuilder AddKeyPerFile(this Microsoft.Extensions.Configuration.IConfigurationBuilder builder, System.Action configureSource) { throw null; } + public static Microsoft.Extensions.Configuration.IConfigurationBuilder AddKeyPerFile(this Microsoft.Extensions.Configuration.IConfigurationBuilder builder, string directoryPath, bool optional) { throw null; } + } +} +namespace Microsoft.Extensions.Configuration.KeyPerFile +{ + public partial class KeyPerFileConfigurationProvider : Microsoft.Extensions.Configuration.ConfigurationProvider + { + public KeyPerFileConfigurationProvider(Microsoft.Extensions.Configuration.KeyPerFile.KeyPerFileConfigurationSource source) { } + public override void Load() { } + public override string ToString() { throw null; } + } + public partial class KeyPerFileConfigurationSource : Microsoft.Extensions.Configuration.IConfigurationSource + { + public KeyPerFileConfigurationSource() { } + public Microsoft.Extensions.FileProviders.IFileProvider FileProvider { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } + public System.Func IgnoreCondition { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } + public string IgnorePrefix { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } + public bool Optional { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } + public Microsoft.Extensions.Configuration.IConfigurationProvider Build(Microsoft.Extensions.Configuration.IConfigurationBuilder builder) { throw null; } + } +} diff --git a/src/FileProviders/Embedded/ref/Microsoft.Extensions.FileProviders.Embedded.csproj b/src/FileProviders/Embedded/ref/Microsoft.Extensions.FileProviders.Embedded.csproj new file mode 100644 index 0000000000..ba6fddca2b --- /dev/null +++ b/src/FileProviders/Embedded/ref/Microsoft.Extensions.FileProviders.Embedded.csproj @@ -0,0 +1,11 @@ + + + + netstandard2.0 + false + + + + + + diff --git a/src/FileProviders/Embedded/ref/Microsoft.Extensions.FileProviders.Embedded.netstandard2.0.cs b/src/FileProviders/Embedded/ref/Microsoft.Extensions.FileProviders.Embedded.netstandard2.0.cs new file mode 100644 index 0000000000..1596f191fd --- /dev/null +++ b/src/FileProviders/Embedded/ref/Microsoft.Extensions.FileProviders.Embedded.netstandard2.0.cs @@ -0,0 +1,39 @@ +// 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. + +namespace Microsoft.Extensions.FileProviders +{ + public partial class EmbeddedFileProvider : Microsoft.Extensions.FileProviders.IFileProvider + { + public EmbeddedFileProvider(System.Reflection.Assembly assembly) { } + public EmbeddedFileProvider(System.Reflection.Assembly assembly, string baseNamespace) { } + public Microsoft.Extensions.FileProviders.IDirectoryContents GetDirectoryContents(string subpath) { throw null; } + public Microsoft.Extensions.FileProviders.IFileInfo GetFileInfo(string subpath) { throw null; } + public Microsoft.Extensions.Primitives.IChangeToken Watch(string pattern) { throw null; } + } + public partial class ManifestEmbeddedFileProvider : Microsoft.Extensions.FileProviders.IFileProvider + { + public ManifestEmbeddedFileProvider(System.Reflection.Assembly assembly) { } + public ManifestEmbeddedFileProvider(System.Reflection.Assembly assembly, string root) { } + public ManifestEmbeddedFileProvider(System.Reflection.Assembly assembly, string root, System.DateTimeOffset lastModified) { } + public ManifestEmbeddedFileProvider(System.Reflection.Assembly assembly, string root, string manifestName, System.DateTimeOffset lastModified) { } + public System.Reflection.Assembly Assembly { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } } + public Microsoft.Extensions.FileProviders.IDirectoryContents GetDirectoryContents(string subpath) { throw null; } + public Microsoft.Extensions.FileProviders.IFileInfo GetFileInfo(string subpath) { throw null; } + public Microsoft.Extensions.Primitives.IChangeToken Watch(string filter) { throw null; } + } +} +namespace Microsoft.Extensions.FileProviders.Embedded +{ + public partial class EmbeddedResourceFileInfo : Microsoft.Extensions.FileProviders.IFileInfo + { + public EmbeddedResourceFileInfo(System.Reflection.Assembly assembly, string resourcePath, string name, System.DateTimeOffset lastModified) { } + public bool Exists { get { throw null; } } + public bool IsDirectory { get { throw null; } } + public System.DateTimeOffset LastModified { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } } + public long Length { get { throw null; } } + public string Name { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } } + public string PhysicalPath { get { throw null; } } + public System.IO.Stream CreateReadStream() { throw null; } + } +} diff --git a/src/HealthChecks/Abstractions/ref/Microsoft.Extensions.Diagnostics.HealthChecks.Abstractions.csproj b/src/HealthChecks/Abstractions/ref/Microsoft.Extensions.Diagnostics.HealthChecks.Abstractions.csproj new file mode 100644 index 0000000000..201f8d515b --- /dev/null +++ b/src/HealthChecks/Abstractions/ref/Microsoft.Extensions.Diagnostics.HealthChecks.Abstractions.csproj @@ -0,0 +1,11 @@ + + + + netstandard2.0 + false + + + + + + diff --git a/src/HealthChecks/Abstractions/ref/Microsoft.Extensions.Diagnostics.HealthChecks.Abstractions.netstandard2.0.cs b/src/HealthChecks/Abstractions/ref/Microsoft.Extensions.Diagnostics.HealthChecks.Abstractions.netstandard2.0.cs new file mode 100644 index 0000000000..c0117ef473 --- /dev/null +++ b/src/HealthChecks/Abstractions/ref/Microsoft.Extensions.Diagnostics.HealthChecks.Abstractions.netstandard2.0.cs @@ -0,0 +1,67 @@ +// 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. + +namespace Microsoft.Extensions.Diagnostics.HealthChecks +{ + public sealed partial class HealthCheckContext + { + public HealthCheckContext() { } + public Microsoft.Extensions.Diagnostics.HealthChecks.HealthCheckRegistration Registration { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } + } + public sealed partial class HealthCheckRegistration + { + public HealthCheckRegistration(string name, Microsoft.Extensions.Diagnostics.HealthChecks.IHealthCheck instance, System.Nullable failureStatus, System.Collections.Generic.IEnumerable tags) { } + public HealthCheckRegistration(string name, System.Func factory, System.Nullable failureStatus, System.Collections.Generic.IEnumerable tags) { } + public System.Func Factory { get { throw null; } set { } } + public Microsoft.Extensions.Diagnostics.HealthChecks.HealthStatus FailureStatus { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } + public string Name { get { throw null; } set { } } + public System.Collections.Generic.ISet Tags { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } } + } + [System.Runtime.InteropServices.StructLayoutAttribute(System.Runtime.InteropServices.LayoutKind.Sequential)] + public partial struct HealthCheckResult + { + private object _dummy; + private int _dummyPrimitive; + public HealthCheckResult(Microsoft.Extensions.Diagnostics.HealthChecks.HealthStatus status, string description = null, System.Exception exception = null, System.Collections.Generic.IReadOnlyDictionary data = null) { throw null; } + public System.Collections.Generic.IReadOnlyDictionary Data { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } } + public string Description { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } } + public System.Exception Exception { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } } + public Microsoft.Extensions.Diagnostics.HealthChecks.HealthStatus Status { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } } + public static Microsoft.Extensions.Diagnostics.HealthChecks.HealthCheckResult Degraded(string description = null, System.Exception exception = null, System.Collections.Generic.IReadOnlyDictionary data = null) { throw null; } + public static Microsoft.Extensions.Diagnostics.HealthChecks.HealthCheckResult Healthy(string description = null, System.Collections.Generic.IReadOnlyDictionary data = null) { throw null; } + public static Microsoft.Extensions.Diagnostics.HealthChecks.HealthCheckResult Unhealthy(string description = null, System.Exception exception = null, System.Collections.Generic.IReadOnlyDictionary data = null) { throw null; } + } + public sealed partial class HealthReport + { + public HealthReport(System.Collections.Generic.IReadOnlyDictionary entries, System.TimeSpan totalDuration) { } + public System.Collections.Generic.IReadOnlyDictionary Entries { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } } + public Microsoft.Extensions.Diagnostics.HealthChecks.HealthStatus Status { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } } + public System.TimeSpan TotalDuration { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } } + } + [System.Runtime.InteropServices.StructLayoutAttribute(System.Runtime.InteropServices.LayoutKind.Sequential)] + public partial struct HealthReportEntry + { + private object _dummy; + private int _dummyPrimitive; + public HealthReportEntry(Microsoft.Extensions.Diagnostics.HealthChecks.HealthStatus status, string description, System.TimeSpan duration, System.Exception exception, System.Collections.Generic.IReadOnlyDictionary data) { throw null; } + public System.Collections.Generic.IReadOnlyDictionary Data { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } } + public string Description { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } } + public System.TimeSpan Duration { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } } + public System.Exception Exception { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } } + public Microsoft.Extensions.Diagnostics.HealthChecks.HealthStatus Status { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } } + } + public enum HealthStatus + { + Degraded = 1, + Healthy = 2, + Unhealthy = 0, + } + public partial interface IHealthCheck + { + System.Threading.Tasks.Task CheckHealthAsync(Microsoft.Extensions.Diagnostics.HealthChecks.HealthCheckContext context, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)); + } + public partial interface IHealthCheckPublisher + { + System.Threading.Tasks.Task PublishAsync(Microsoft.Extensions.Diagnostics.HealthChecks.HealthReport report, System.Threading.CancellationToken cancellationToken); + } +} diff --git a/src/HealthChecks/HealthChecks/ref/Microsoft.Extensions.Diagnostics.HealthChecks.csproj b/src/HealthChecks/HealthChecks/ref/Microsoft.Extensions.Diagnostics.HealthChecks.csproj new file mode 100644 index 0000000000..925e89cfa0 --- /dev/null +++ b/src/HealthChecks/HealthChecks/ref/Microsoft.Extensions.Diagnostics.HealthChecks.csproj @@ -0,0 +1,13 @@ + + + + netstandard2.0 + false + + + + + + + + diff --git a/src/HealthChecks/HealthChecks/ref/Microsoft.Extensions.Diagnostics.HealthChecks.netstandard2.0.cs b/src/HealthChecks/HealthChecks/ref/Microsoft.Extensions.Diagnostics.HealthChecks.netstandard2.0.cs new file mode 100644 index 0000000000..89281584e2 --- /dev/null +++ b/src/HealthChecks/HealthChecks/ref/Microsoft.Extensions.Diagnostics.HealthChecks.netstandard2.0.cs @@ -0,0 +1,52 @@ +// 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. + +namespace Microsoft.Extensions.DependencyInjection +{ + public static partial class HealthChecksBuilderAddCheckExtensions + { + public static Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder AddCheck(this Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder builder, string name, Microsoft.Extensions.Diagnostics.HealthChecks.IHealthCheck instance, System.Nullable failureStatus = default(System.Nullable), System.Collections.Generic.IEnumerable tags = null) { throw null; } + public static Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder AddCheck(this Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder builder, string name, System.Nullable failureStatus = default(System.Nullable), System.Collections.Generic.IEnumerable tags = null) where T : class, Microsoft.Extensions.Diagnostics.HealthChecks.IHealthCheck { throw null; } + public static Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder AddTypeActivatedCheck(this Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder builder, string name, System.Nullable failureStatus, System.Collections.Generic.IEnumerable tags, params object[] args) where T : class, Microsoft.Extensions.Diagnostics.HealthChecks.IHealthCheck { throw null; } + public static Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder AddTypeActivatedCheck(this Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder builder, string name, System.Nullable failureStatus, params object[] args) where T : class, Microsoft.Extensions.Diagnostics.HealthChecks.IHealthCheck { throw null; } + public static Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder AddTypeActivatedCheck(this Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder builder, string name, params object[] args) where T : class, Microsoft.Extensions.Diagnostics.HealthChecks.IHealthCheck { throw null; } + } + public static partial class HealthChecksBuilderDelegateExtensions + { + public static Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder AddAsyncCheck(this Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder builder, string name, System.Func> check, System.Collections.Generic.IEnumerable tags = null) { throw null; } + public static Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder AddAsyncCheck(this Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder builder, string name, System.Func> check, System.Collections.Generic.IEnumerable tags = null) { throw null; } + public static Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder AddCheck(this Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder builder, string name, System.Func check, System.Collections.Generic.IEnumerable tags = null) { throw null; } + public static Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder AddCheck(this Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder builder, string name, System.Func check, System.Collections.Generic.IEnumerable tags = null) { throw null; } + } + public static partial class HealthCheckServiceCollectionExtensions + { + public static Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder AddHealthChecks(this Microsoft.Extensions.DependencyInjection.IServiceCollection services) { throw null; } + } + public partial interface IHealthChecksBuilder + { + Microsoft.Extensions.DependencyInjection.IServiceCollection Services { get; } + Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder Add(Microsoft.Extensions.Diagnostics.HealthChecks.HealthCheckRegistration registration); + } +} +namespace Microsoft.Extensions.Diagnostics.HealthChecks +{ + public sealed partial class HealthCheckPublisherOptions + { + public HealthCheckPublisherOptions() { } + public System.TimeSpan Delay { get { throw null; } set { } } + public System.TimeSpan Period { get { throw null; } set { } } + public System.Func Predicate { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } + public System.TimeSpan Timeout { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } + } + public abstract partial class HealthCheckService + { + protected HealthCheckService() { } + public abstract System.Threading.Tasks.Task CheckHealthAsync(System.Func predicate, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)); + public System.Threading.Tasks.Task CheckHealthAsync(System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) { throw null; } + } + public sealed partial class HealthCheckServiceOptions + { + public HealthCheckServiceOptions() { } + public System.Collections.Generic.ICollection Registrations { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } } + } +} diff --git a/src/JSInterop/Microsoft.JSInterop/ref/Microsoft.JSInterop.csproj b/src/JSInterop/Microsoft.JSInterop/ref/Microsoft.JSInterop.csproj new file mode 100644 index 0000000000..a59b30a32e --- /dev/null +++ b/src/JSInterop/Microsoft.JSInterop/ref/Microsoft.JSInterop.csproj @@ -0,0 +1,11 @@ + + + + netstandard2.0 + false + + + + + + diff --git a/src/JSInterop/Microsoft.JSInterop/ref/Microsoft.JSInterop.netstandard2.0.cs b/src/JSInterop/Microsoft.JSInterop/ref/Microsoft.JSInterop.netstandard2.0.cs new file mode 100644 index 0000000000..02146c7bab --- /dev/null +++ b/src/JSInterop/Microsoft.JSInterop/ref/Microsoft.JSInterop.netstandard2.0.cs @@ -0,0 +1,76 @@ +// 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. + +namespace Microsoft.JSInterop +{ + public static partial class DotNetDispatcher + { + public static void BeginInvoke(string callId, string assemblyName, string methodIdentifier, long dotNetObjectId, string argsJson) { } + [Microsoft.JSInterop.JSInvokableAttribute("DotNetDispatcher.EndInvoke")] + public static void EndInvoke(long asyncHandle, bool succeeded, Microsoft.JSInterop.Internal.JSAsyncCallResult result) { } + public static string Invoke(string assemblyName, string methodIdentifier, long dotNetObjectId, string argsJson) { throw null; } + [Microsoft.JSInterop.JSInvokableAttribute("DotNetDispatcher.ReleaseDotNetObject")] + public static void ReleaseDotNetObject(long dotNetObjectId) { } + } + public partial class DotNetObjectRef : System.IDisposable + { + public DotNetObjectRef(object value) { } + public object Value { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } } + public void Dispose() { } + public void EnsureAttachedToJsRuntime(Microsoft.JSInterop.IJSRuntime runtime) { } + } + public partial interface IJSInProcessRuntime : Microsoft.JSInterop.IJSRuntime + { + T Invoke(string identifier, params object[] args); + } + public partial interface IJSRuntime + { + System.Threading.Tasks.Task InvokeAsync(string identifier, params object[] args); + void UntrackObjectRef(Microsoft.JSInterop.DotNetObjectRef dotNetObjectRef); + } + public partial class JSException : System.Exception + { + public JSException(string message) { } + } + public abstract partial class JSInProcessRuntimeBase : Microsoft.JSInterop.JSRuntimeBase, Microsoft.JSInterop.IJSInProcessRuntime, Microsoft.JSInterop.IJSRuntime + { + protected JSInProcessRuntimeBase() { } + protected abstract string InvokeJS(string identifier, string argsJson); + public T Invoke(string identifier, params object[] args) { throw null; } + } + [System.AttributeUsageAttribute(System.AttributeTargets.Method, AllowMultiple=true)] + public partial class JSInvokableAttribute : System.Attribute + { + public JSInvokableAttribute() { } + public JSInvokableAttribute(string identifier) { } + public string Identifier { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } } + } + public static partial class Json + { + public static T Deserialize(string json) { throw null; } + public static string Serialize(object value) { throw null; } + } + public static partial class JSRuntime + { + public static Microsoft.JSInterop.IJSRuntime Current { get { throw null; } } + public static void SetCurrentJSRuntime(Microsoft.JSInterop.IJSRuntime instance) { } + } + public abstract partial class JSRuntimeBase : Microsoft.JSInterop.IJSRuntime + { + public JSRuntimeBase() { } + protected abstract void BeginInvokeJS(long asyncHandle, string identifier, string argsJson); + public System.Threading.Tasks.Task InvokeAsync(string identifier, params object[] args) { throw null; } + public void UntrackObjectRef(Microsoft.JSInterop.DotNetObjectRef dotNetObjectRef) { } + } +} +namespace Microsoft.JSInterop.Internal +{ + public partial interface ICustomArgSerializer + { + object ToJsonPrimitive(); + } + public partial class JSAsyncCallResult + { + internal JSAsyncCallResult() { } + } +} diff --git a/src/JSInterop/Mono.WebAssembly.Interop/ref/Mono.WebAssembly.Interop.csproj b/src/JSInterop/Mono.WebAssembly.Interop/ref/Mono.WebAssembly.Interop.csproj new file mode 100644 index 0000000000..49f8622c83 --- /dev/null +++ b/src/JSInterop/Mono.WebAssembly.Interop/ref/Mono.WebAssembly.Interop.csproj @@ -0,0 +1,11 @@ + + + + netstandard2.0 + false + + + + + + diff --git a/src/JSInterop/Mono.WebAssembly.Interop/ref/Mono.WebAssembly.Interop.netstandard2.0.cs b/src/JSInterop/Mono.WebAssembly.Interop/ref/Mono.WebAssembly.Interop.netstandard2.0.cs new file mode 100644 index 0000000000..033e97237a --- /dev/null +++ b/src/JSInterop/Mono.WebAssembly.Interop/ref/Mono.WebAssembly.Interop.netstandard2.0.cs @@ -0,0 +1,16 @@ +// 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. + +namespace Mono.WebAssembly.Interop +{ + public partial class MonoWebAssemblyJSRuntime : Microsoft.JSInterop.JSInProcessRuntimeBase + { + public MonoWebAssemblyJSRuntime() { } + protected override void BeginInvokeJS(long asyncHandle, string identifier, string argsJson) { } + protected override string InvokeJS(string identifier, string argsJson) { throw null; } + public TRes InvokeUnmarshalled(string identifier) { throw null; } + public TRes InvokeUnmarshalled(string identifier, T0 arg0) { throw null; } + public TRes InvokeUnmarshalled(string identifier, T0 arg0, T1 arg1) { throw null; } + public TRes InvokeUnmarshalled(string identifier, T0 arg0, T1 arg1, T2 arg2) { throw null; } + } +} diff --git a/src/Localization/Abstractions/ref/Microsoft.Extensions.Localization.Abstractions.csproj b/src/Localization/Abstractions/ref/Microsoft.Extensions.Localization.Abstractions.csproj new file mode 100644 index 0000000000..e526fd4fb8 --- /dev/null +++ b/src/Localization/Abstractions/ref/Microsoft.Extensions.Localization.Abstractions.csproj @@ -0,0 +1,11 @@ + + + + netstandard2.0 + false + + + + + + diff --git a/src/Localization/Abstractions/ref/Microsoft.Extensions.Localization.Abstractions.netstandard2.0.cs b/src/Localization/Abstractions/ref/Microsoft.Extensions.Localization.Abstractions.netstandard2.0.cs new file mode 100644 index 0000000000..b18cec35f8 --- /dev/null +++ b/src/Localization/Abstractions/ref/Microsoft.Extensions.Localization.Abstractions.netstandard2.0.cs @@ -0,0 +1,47 @@ +// 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. + +namespace Microsoft.Extensions.Localization +{ + public partial interface IStringLocalizer + { + Microsoft.Extensions.Localization.LocalizedString this[string name] { get; } + Microsoft.Extensions.Localization.LocalizedString this[string name, params object[] arguments] { get; } + System.Collections.Generic.IEnumerable GetAllStrings(bool includeParentCultures); + Microsoft.Extensions.Localization.IStringLocalizer WithCulture(System.Globalization.CultureInfo culture); + } + public partial interface IStringLocalizerFactory + { + Microsoft.Extensions.Localization.IStringLocalizer Create(string baseName, string location); + Microsoft.Extensions.Localization.IStringLocalizer Create(System.Type resourceSource); + } + public partial interface IStringLocalizer : Microsoft.Extensions.Localization.IStringLocalizer + { + } + public partial class LocalizedString + { + public LocalizedString(string name, string value) { } + public LocalizedString(string name, string value, bool resourceNotFound) { } + public LocalizedString(string name, string value, bool resourceNotFound, string searchedLocation) { } + public string Name { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } } + public bool ResourceNotFound { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } } + public string SearchedLocation { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } } + public string Value { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } } + public static implicit operator string (Microsoft.Extensions.Localization.LocalizedString localizedString) { throw null; } + public override string ToString() { throw null; } + } + public static partial class StringLocalizerExtensions + { + public static System.Collections.Generic.IEnumerable GetAllStrings(this Microsoft.Extensions.Localization.IStringLocalizer stringLocalizer) { throw null; } + public static Microsoft.Extensions.Localization.LocalizedString GetString(this Microsoft.Extensions.Localization.IStringLocalizer stringLocalizer, string name) { throw null; } + public static Microsoft.Extensions.Localization.LocalizedString GetString(this Microsoft.Extensions.Localization.IStringLocalizer stringLocalizer, string name, params object[] arguments) { throw null; } + } + public partial class StringLocalizer : Microsoft.Extensions.Localization.IStringLocalizer, Microsoft.Extensions.Localization.IStringLocalizer + { + public StringLocalizer(Microsoft.Extensions.Localization.IStringLocalizerFactory factory) { } + public virtual Microsoft.Extensions.Localization.LocalizedString this[string name] { get { throw null; } } + public virtual Microsoft.Extensions.Localization.LocalizedString this[string name, params object[] arguments] { get { throw null; } } + public System.Collections.Generic.IEnumerable GetAllStrings(bool includeParentCultures) { throw null; } + public virtual Microsoft.Extensions.Localization.IStringLocalizer WithCulture(System.Globalization.CultureInfo culture) { throw null; } + } +} diff --git a/src/Localization/Localization/ref/Microsoft.Extensions.Localization.csproj b/src/Localization/Localization/ref/Microsoft.Extensions.Localization.csproj new file mode 100644 index 0000000000..d48c510f7d --- /dev/null +++ b/src/Localization/Localization/ref/Microsoft.Extensions.Localization.csproj @@ -0,0 +1,14 @@ + + + + netstandard2.0 + false + + + + + + + + + diff --git a/src/Localization/Localization/ref/Microsoft.Extensions.Localization.netstandard2.0.cs b/src/Localization/Localization/ref/Microsoft.Extensions.Localization.netstandard2.0.cs new file mode 100644 index 0000000000..7bbe1797a5 --- /dev/null +++ b/src/Localization/Localization/ref/Microsoft.Extensions.Localization.netstandard2.0.cs @@ -0,0 +1,91 @@ +// 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. + +namespace Microsoft.Extensions.DependencyInjection +{ + public static partial class LocalizationServiceCollectionExtensions + { + public static Microsoft.Extensions.DependencyInjection.IServiceCollection AddLocalization(this Microsoft.Extensions.DependencyInjection.IServiceCollection services) { throw null; } + public static Microsoft.Extensions.DependencyInjection.IServiceCollection AddLocalization(this Microsoft.Extensions.DependencyInjection.IServiceCollection services, System.Action setupAction) { throw null; } + } +} +namespace Microsoft.Extensions.Localization +{ + public partial interface IResourceNamesCache + { + System.Collections.Generic.IList GetOrAdd(string name, System.Func> valueFactory); + } + public partial class LocalizationOptions + { + public LocalizationOptions() { } + public string ResourcesPath { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } + } + [System.AttributeUsageAttribute(System.AttributeTargets.Assembly, AllowMultiple=false, Inherited=false)] + public partial class ResourceLocationAttribute : System.Attribute + { + public ResourceLocationAttribute(string resourceLocation) { } + public string ResourceLocation { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } } + } + public partial class ResourceManagerStringLocalizer : Microsoft.Extensions.Localization.IStringLocalizer + { + public ResourceManagerStringLocalizer(System.Resources.ResourceManager resourceManager, Microsoft.Extensions.Localization.Internal.AssemblyWrapper resourceAssemblyWrapper, string baseName, Microsoft.Extensions.Localization.IResourceNamesCache resourceNamesCache, Microsoft.Extensions.Logging.ILogger logger) { } + public ResourceManagerStringLocalizer(System.Resources.ResourceManager resourceManager, Microsoft.Extensions.Localization.Internal.IResourceStringProvider resourceStringProvider, string baseName, Microsoft.Extensions.Localization.IResourceNamesCache resourceNamesCache, Microsoft.Extensions.Logging.ILogger logger) { } + public ResourceManagerStringLocalizer(System.Resources.ResourceManager resourceManager, System.Reflection.Assembly resourceAssembly, string baseName, Microsoft.Extensions.Localization.IResourceNamesCache resourceNamesCache, Microsoft.Extensions.Logging.ILogger logger) { } + public virtual Microsoft.Extensions.Localization.LocalizedString this[string name] { get { throw null; } } + public virtual Microsoft.Extensions.Localization.LocalizedString this[string name, params object[] arguments] { get { throw null; } } + public virtual System.Collections.Generic.IEnumerable GetAllStrings(bool includeParentCultures) { throw null; } + protected System.Collections.Generic.IEnumerable GetAllStrings(bool includeParentCultures, System.Globalization.CultureInfo culture) { throw null; } + protected string GetStringSafely(string name, System.Globalization.CultureInfo culture) { throw null; } + public Microsoft.Extensions.Localization.IStringLocalizer WithCulture(System.Globalization.CultureInfo culture) { throw null; } + } + public partial class ResourceManagerStringLocalizerFactory : Microsoft.Extensions.Localization.IStringLocalizerFactory + { + public ResourceManagerStringLocalizerFactory(Microsoft.Extensions.Options.IOptions localizationOptions, Microsoft.Extensions.Logging.ILoggerFactory loggerFactory) { } + public Microsoft.Extensions.Localization.IStringLocalizer Create(string baseName, string location) { throw null; } + public Microsoft.Extensions.Localization.IStringLocalizer Create(System.Type resourceSource) { throw null; } + protected virtual Microsoft.Extensions.Localization.ResourceManagerStringLocalizer CreateResourceManagerStringLocalizer(System.Reflection.Assembly assembly, string baseName) { throw null; } + protected virtual Microsoft.Extensions.Localization.ResourceLocationAttribute GetResourceLocationAttribute(System.Reflection.Assembly assembly) { throw null; } + protected virtual string GetResourcePrefix(System.Reflection.TypeInfo typeInfo) { throw null; } + protected virtual string GetResourcePrefix(System.Reflection.TypeInfo typeInfo, string baseNamespace, string resourcesRelativePath) { throw null; } + protected virtual string GetResourcePrefix(string baseResourceName, string baseNamespace) { throw null; } + protected virtual string GetResourcePrefix(string location, string baseName, string resourceLocation) { throw null; } + protected virtual Microsoft.Extensions.Localization.RootNamespaceAttribute GetRootNamespaceAttribute(System.Reflection.Assembly assembly) { throw null; } + } + public partial class ResourceManagerWithCultureStringLocalizer : Microsoft.Extensions.Localization.ResourceManagerStringLocalizer + { + public ResourceManagerWithCultureStringLocalizer(System.Resources.ResourceManager resourceManager, System.Reflection.Assembly resourceAssembly, string baseName, Microsoft.Extensions.Localization.IResourceNamesCache resourceNamesCache, System.Globalization.CultureInfo culture, Microsoft.Extensions.Logging.ILogger logger) : base (default(System.Resources.ResourceManager), default(System.Reflection.Assembly), default(string), default(Microsoft.Extensions.Localization.IResourceNamesCache), default(Microsoft.Extensions.Logging.ILogger)) { } + public override Microsoft.Extensions.Localization.LocalizedString this[string name] { get { throw null; } } + public override Microsoft.Extensions.Localization.LocalizedString this[string name, params object[] arguments] { get { throw null; } } + public override System.Collections.Generic.IEnumerable GetAllStrings(bool includeParentCultures) { throw null; } + } + public partial class ResourceNamesCache : Microsoft.Extensions.Localization.IResourceNamesCache + { + public ResourceNamesCache() { } + public System.Collections.Generic.IList GetOrAdd(string name, System.Func> valueFactory) { throw null; } + } + [System.AttributeUsageAttribute(System.AttributeTargets.Assembly, AllowMultiple=false, Inherited=false)] + public partial class RootNamespaceAttribute : System.Attribute + { + public RootNamespaceAttribute(string rootNamespace) { } + public string RootNamespace { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } } + } +} +namespace Microsoft.Extensions.Localization.Internal +{ + public partial class AssemblyWrapper + { + public AssemblyWrapper(System.Reflection.Assembly assembly) { } + public System.Reflection.Assembly Assembly { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } } + public virtual string FullName { get { throw null; } } + public virtual System.IO.Stream GetManifestResourceStream(string name) { throw null; } + } + public partial interface IResourceStringProvider + { + System.Collections.Generic.IList GetAllResourceStrings(System.Globalization.CultureInfo culture, bool throwOnMissing); + } + public partial class ResourceManagerStringProvider : Microsoft.Extensions.Localization.Internal.IResourceStringProvider + { + public ResourceManagerStringProvider(Microsoft.Extensions.Localization.IResourceNamesCache resourceCache, System.Resources.ResourceManager resourceManager, System.Reflection.Assembly assembly, string baseName) { } + public System.Collections.Generic.IList GetAllResourceStrings(System.Globalization.CultureInfo culture, bool throwOnMissing) { throw null; } + } +} diff --git a/src/ObjectPool/ref/Microsoft.Extensions.ObjectPool.csproj b/src/ObjectPool/ref/Microsoft.Extensions.ObjectPool.csproj new file mode 100644 index 0000000000..8ae73841b1 --- /dev/null +++ b/src/ObjectPool/ref/Microsoft.Extensions.ObjectPool.csproj @@ -0,0 +1,11 @@ + + + + netstandard2.0 + false + + + + + + diff --git a/src/ObjectPool/ref/Microsoft.Extensions.ObjectPool.netstandard2.0.cs b/src/ObjectPool/ref/Microsoft.Extensions.ObjectPool.netstandard2.0.cs new file mode 100644 index 0000000000..16fd8379a6 --- /dev/null +++ b/src/ObjectPool/ref/Microsoft.Extensions.ObjectPool.netstandard2.0.cs @@ -0,0 +1,72 @@ +// 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. + +namespace Microsoft.Extensions.ObjectPool +{ + public partial class DefaultObjectPoolProvider : Microsoft.Extensions.ObjectPool.ObjectPoolProvider + { + public DefaultObjectPoolProvider() { } + public int MaximumRetained { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } + public override Microsoft.Extensions.ObjectPool.ObjectPool Create(Microsoft.Extensions.ObjectPool.IPooledObjectPolicy policy) { throw null; } + } + public partial class DefaultObjectPool : Microsoft.Extensions.ObjectPool.ObjectPool where T : class + { + public DefaultObjectPool(Microsoft.Extensions.ObjectPool.IPooledObjectPolicy policy) { } + public DefaultObjectPool(Microsoft.Extensions.ObjectPool.IPooledObjectPolicy policy, int maximumRetained) { } + public override T Get() { throw null; } + public override void Return(T obj) { } + } + public partial class DefaultPooledObjectPolicy : Microsoft.Extensions.ObjectPool.PooledObjectPolicy where T : class, new() + { + public DefaultPooledObjectPolicy() { } + public override T Create() { throw null; } + public override bool Return(T obj) { throw null; } + } + public partial interface IPooledObjectPolicy + { + T Create(); + bool Return(T obj); + } + public partial class LeakTrackingObjectPoolProvider : Microsoft.Extensions.ObjectPool.ObjectPoolProvider + { + public LeakTrackingObjectPoolProvider(Microsoft.Extensions.ObjectPool.ObjectPoolProvider inner) { } + public override Microsoft.Extensions.ObjectPool.ObjectPool Create(Microsoft.Extensions.ObjectPool.IPooledObjectPolicy policy) { throw null; } + } + public partial class LeakTrackingObjectPool : Microsoft.Extensions.ObjectPool.ObjectPool where T : class + { + public LeakTrackingObjectPool(Microsoft.Extensions.ObjectPool.ObjectPool inner) { } + public override T Get() { throw null; } + public override void Return(T obj) { } + } + public abstract partial class ObjectPoolProvider + { + protected ObjectPoolProvider() { } + public Microsoft.Extensions.ObjectPool.ObjectPool Create() where T : class, new() { throw null; } + public abstract Microsoft.Extensions.ObjectPool.ObjectPool Create(Microsoft.Extensions.ObjectPool.IPooledObjectPolicy policy) where T : class; + } + public static partial class ObjectPoolProviderExtensions + { + public static Microsoft.Extensions.ObjectPool.ObjectPool CreateStringBuilderPool(this Microsoft.Extensions.ObjectPool.ObjectPoolProvider provider) { throw null; } + public static Microsoft.Extensions.ObjectPool.ObjectPool CreateStringBuilderPool(this Microsoft.Extensions.ObjectPool.ObjectPoolProvider provider, int initialCapacity, int maximumRetainedCapacity) { throw null; } + } + public abstract partial class ObjectPool where T : class + { + protected ObjectPool() { } + public abstract T Get(); + public abstract void Return(T obj); + } + public abstract partial class PooledObjectPolicy : Microsoft.Extensions.ObjectPool.IPooledObjectPolicy + { + protected PooledObjectPolicy() { } + public abstract T Create(); + public abstract bool Return(T obj); + } + public partial class StringBuilderPooledObjectPolicy : Microsoft.Extensions.ObjectPool.PooledObjectPolicy + { + public StringBuilderPooledObjectPolicy() { } + public int InitialCapacity { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } + public int MaximumRetainedCapacity { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } + public override System.Text.StringBuilder Create() { throw null; } + public override bool Return(System.Text.StringBuilder obj) { throw null; } + } +} diff --git a/src/WebEncoders/Directory.Build.props b/src/WebEncoders/Directory.Build.props new file mode 100644 index 0000000000..81557e1bca --- /dev/null +++ b/src/WebEncoders/Directory.Build.props @@ -0,0 +1,8 @@ + + + + + true + + + diff --git a/src/WebEncoders/ref/Microsoft.Extensions.WebEncoders.csproj b/src/WebEncoders/ref/Microsoft.Extensions.WebEncoders.csproj new file mode 100644 index 0000000000..d102a7d31b --- /dev/null +++ b/src/WebEncoders/ref/Microsoft.Extensions.WebEncoders.csproj @@ -0,0 +1,13 @@ + + + + netstandard2.0 + false + + + + + + + + diff --git a/src/WebEncoders/ref/Microsoft.Extensions.WebEncoders.netstandard2.0.cs b/src/WebEncoders/ref/Microsoft.Extensions.WebEncoders.netstandard2.0.cs new file mode 100644 index 0000000000..18cdcbdfa3 --- /dev/null +++ b/src/WebEncoders/ref/Microsoft.Extensions.WebEncoders.netstandard2.0.cs @@ -0,0 +1,55 @@ +// 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. + +namespace Microsoft.Extensions.DependencyInjection +{ + public static partial class EncoderServiceCollectionExtensions + { + public static Microsoft.Extensions.DependencyInjection.IServiceCollection AddWebEncoders(this Microsoft.Extensions.DependencyInjection.IServiceCollection services) { throw null; } + public static Microsoft.Extensions.DependencyInjection.IServiceCollection AddWebEncoders(this Microsoft.Extensions.DependencyInjection.IServiceCollection services, System.Action setupAction) { throw null; } + } +} +namespace Microsoft.Extensions.WebEncoders +{ + public sealed partial class WebEncoderOptions + { + public WebEncoderOptions() { } + public System.Text.Encodings.Web.TextEncoderSettings TextEncoderSettings { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } + } +} +namespace Microsoft.Extensions.WebEncoders.Testing +{ + public sealed partial class HtmlTestEncoder : System.Text.Encodings.Web.HtmlEncoder + { + public HtmlTestEncoder() { } + public override int MaxOutputCharactersPerInputCharacter { get { throw null; } } + public override void Encode(System.IO.TextWriter output, char[] value, int startIndex, int characterCount) { } + public override void Encode(System.IO.TextWriter output, string value, int startIndex, int characterCount) { } + public override string Encode(string value) { throw null; } + public unsafe override int FindFirstCharacterToEncode(char* text, int textLength) { throw null; } + public unsafe override bool TryEncodeUnicodeScalar(int unicodeScalar, char* buffer, int bufferLength, out int numberOfCharactersWritten) { throw null; } + public override bool WillEncode(int unicodeScalar) { throw null; } + } + public partial class JavaScriptTestEncoder : System.Text.Encodings.Web.JavaScriptEncoder + { + public JavaScriptTestEncoder() { } + public override int MaxOutputCharactersPerInputCharacter { get { throw null; } } + public override void Encode(System.IO.TextWriter output, char[] value, int startIndex, int characterCount) { } + public override void Encode(System.IO.TextWriter output, string value, int startIndex, int characterCount) { } + public override string Encode(string value) { throw null; } + public unsafe override int FindFirstCharacterToEncode(char* text, int textLength) { throw null; } + public unsafe override bool TryEncodeUnicodeScalar(int unicodeScalar, char* buffer, int bufferLength, out int numberOfCharactersWritten) { throw null; } + public override bool WillEncode(int unicodeScalar) { throw null; } + } + public partial class UrlTestEncoder : System.Text.Encodings.Web.UrlEncoder + { + public UrlTestEncoder() { } + public override int MaxOutputCharactersPerInputCharacter { get { throw null; } } + public override void Encode(System.IO.TextWriter output, char[] value, int startIndex, int characterCount) { } + public override void Encode(System.IO.TextWriter output, string value, int startIndex, int characterCount) { } + public override string Encode(string value) { throw null; } + public unsafe override int FindFirstCharacterToEncode(char* text, int textLength) { throw null; } + public unsafe override bool TryEncodeUnicodeScalar(int unicodeScalar, char* buffer, int bufferLength, out int numberOfCharactersWritten) { throw null; } + public override bool WillEncode(int unicodeScalar) { throw null; } + } +} diff --git a/src/WebEncoders/src/Microsoft.Extensions.WebEncoders.csproj b/src/WebEncoders/src/Microsoft.Extensions.WebEncoders.csproj index dfc575c73f..8f60f8f983 100644 --- a/src/WebEncoders/src/Microsoft.Extensions.WebEncoders.csproj +++ b/src/WebEncoders/src/Microsoft.Extensions.WebEncoders.csproj @@ -4,7 +4,6 @@ Contains registration and configuration APIs to add the core framework encoders to a dependency injection container. netstandard2.0 $(NoWarn);CS1591 - true true aspnetcore true From dfb597732491b3d941b47a23dbeaf41c4eb07009 Mon Sep 17 00:00:00 2001 From: Glenn Condron Date: Mon, 4 Feb 2019 15:14:37 -0800 Subject: [PATCH 0058/1101] Update HealthCheckPublisherOptions.cs This seems wrong. Presumably fixes: https://github.com/aspnet/Extensions/issues/1041\n\nCommit migrated from https://github.com/dotnet/extensions/commit/b3c88d78fe112bc3b2e272299156857a383edf5d --- .../HealthChecks/src/HealthCheckPublisherOptions.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/HealthChecks/HealthChecks/src/HealthCheckPublisherOptions.cs b/src/HealthChecks/HealthChecks/src/HealthCheckPublisherOptions.cs index 1313718af8..6b7c8c3365 100644 --- a/src/HealthChecks/HealthChecks/src/HealthCheckPublisherOptions.cs +++ b/src/HealthChecks/HealthChecks/src/HealthCheckPublisherOptions.cs @@ -60,7 +60,7 @@ namespace Microsoft.Extensions.Diagnostics.HealthChecks throw new ArgumentException($"The {nameof(Period)} must not be infinite.", nameof(value)); } - _delay = value; + _period = value; } } From c960040b533ae026b66adceda72dd5203d2bc5dc Mon Sep 17 00:00:00 2001 From: Chris Ross Date: Fri, 15 Feb 2019 13:40:33 -0800 Subject: [PATCH 0059/1101] Deprecate and replace IHostingEnvronment & IApplicationLifetime (dotnet/extensions#1100) * Deprecate and replace IHostingEnvronment & IApplicationLifetime dotnet/extensions#966 * Fix startvs * Fix ref generation for obosolete\n\nCommit migrated from https://github.com/dotnet/extensions/commit/6991e2b0e8f7a133208d015dc7d0ca4192523ba0 --- .../ref/Microsoft.Extensions.Configuration.KeyPerFile.csproj | 1 - .../ref/Microsoft.Extensions.FileProviders.Embedded.csproj | 1 - ...osoft.Extensions.Diagnostics.HealthChecks.Abstractions.csproj | 1 - .../ref/Microsoft.Extensions.Diagnostics.HealthChecks.csproj | 1 - src/JSInterop/Microsoft.JSInterop/ref/Microsoft.JSInterop.csproj | 1 - .../Mono.WebAssembly.Interop/ref/Mono.WebAssembly.Interop.csproj | 1 - .../ref/Microsoft.Extensions.Localization.Abstractions.csproj | 1 - .../Localization/ref/Microsoft.Extensions.Localization.csproj | 1 - src/ObjectPool/ref/Microsoft.Extensions.ObjectPool.csproj | 1 - src/WebEncoders/ref/Microsoft.Extensions.WebEncoders.csproj | 1 - 10 files changed, 10 deletions(-) diff --git a/src/Configuration.KeyPerFile/ref/Microsoft.Extensions.Configuration.KeyPerFile.csproj b/src/Configuration.KeyPerFile/ref/Microsoft.Extensions.Configuration.KeyPerFile.csproj index 3df73aba0c..21f0053e59 100644 --- a/src/Configuration.KeyPerFile/ref/Microsoft.Extensions.Configuration.KeyPerFile.csproj +++ b/src/Configuration.KeyPerFile/ref/Microsoft.Extensions.Configuration.KeyPerFile.csproj @@ -2,7 +2,6 @@ netstandard2.0 - false diff --git a/src/FileProviders/Embedded/ref/Microsoft.Extensions.FileProviders.Embedded.csproj b/src/FileProviders/Embedded/ref/Microsoft.Extensions.FileProviders.Embedded.csproj index ba6fddca2b..b8f2f33387 100644 --- a/src/FileProviders/Embedded/ref/Microsoft.Extensions.FileProviders.Embedded.csproj +++ b/src/FileProviders/Embedded/ref/Microsoft.Extensions.FileProviders.Embedded.csproj @@ -2,7 +2,6 @@ netstandard2.0 - false diff --git a/src/HealthChecks/Abstractions/ref/Microsoft.Extensions.Diagnostics.HealthChecks.Abstractions.csproj b/src/HealthChecks/Abstractions/ref/Microsoft.Extensions.Diagnostics.HealthChecks.Abstractions.csproj index 201f8d515b..be23858955 100644 --- a/src/HealthChecks/Abstractions/ref/Microsoft.Extensions.Diagnostics.HealthChecks.Abstractions.csproj +++ b/src/HealthChecks/Abstractions/ref/Microsoft.Extensions.Diagnostics.HealthChecks.Abstractions.csproj @@ -2,7 +2,6 @@ netstandard2.0 - false diff --git a/src/HealthChecks/HealthChecks/ref/Microsoft.Extensions.Diagnostics.HealthChecks.csproj b/src/HealthChecks/HealthChecks/ref/Microsoft.Extensions.Diagnostics.HealthChecks.csproj index 925e89cfa0..277e60910f 100644 --- a/src/HealthChecks/HealthChecks/ref/Microsoft.Extensions.Diagnostics.HealthChecks.csproj +++ b/src/HealthChecks/HealthChecks/ref/Microsoft.Extensions.Diagnostics.HealthChecks.csproj @@ -2,7 +2,6 @@ netstandard2.0 - false diff --git a/src/JSInterop/Microsoft.JSInterop/ref/Microsoft.JSInterop.csproj b/src/JSInterop/Microsoft.JSInterop/ref/Microsoft.JSInterop.csproj index a59b30a32e..87fd913427 100644 --- a/src/JSInterop/Microsoft.JSInterop/ref/Microsoft.JSInterop.csproj +++ b/src/JSInterop/Microsoft.JSInterop/ref/Microsoft.JSInterop.csproj @@ -2,7 +2,6 @@ netstandard2.0 - false diff --git a/src/JSInterop/Mono.WebAssembly.Interop/ref/Mono.WebAssembly.Interop.csproj b/src/JSInterop/Mono.WebAssembly.Interop/ref/Mono.WebAssembly.Interop.csproj index 49f8622c83..a1ba605705 100644 --- a/src/JSInterop/Mono.WebAssembly.Interop/ref/Mono.WebAssembly.Interop.csproj +++ b/src/JSInterop/Mono.WebAssembly.Interop/ref/Mono.WebAssembly.Interop.csproj @@ -2,7 +2,6 @@ netstandard2.0 - false diff --git a/src/Localization/Abstractions/ref/Microsoft.Extensions.Localization.Abstractions.csproj b/src/Localization/Abstractions/ref/Microsoft.Extensions.Localization.Abstractions.csproj index e526fd4fb8..0608c06147 100644 --- a/src/Localization/Abstractions/ref/Microsoft.Extensions.Localization.Abstractions.csproj +++ b/src/Localization/Abstractions/ref/Microsoft.Extensions.Localization.Abstractions.csproj @@ -2,7 +2,6 @@ netstandard2.0 - false diff --git a/src/Localization/Localization/ref/Microsoft.Extensions.Localization.csproj b/src/Localization/Localization/ref/Microsoft.Extensions.Localization.csproj index d48c510f7d..d628c33a54 100644 --- a/src/Localization/Localization/ref/Microsoft.Extensions.Localization.csproj +++ b/src/Localization/Localization/ref/Microsoft.Extensions.Localization.csproj @@ -2,7 +2,6 @@ netstandard2.0 - false diff --git a/src/ObjectPool/ref/Microsoft.Extensions.ObjectPool.csproj b/src/ObjectPool/ref/Microsoft.Extensions.ObjectPool.csproj index 8ae73841b1..1fbb81a9ca 100644 --- a/src/ObjectPool/ref/Microsoft.Extensions.ObjectPool.csproj +++ b/src/ObjectPool/ref/Microsoft.Extensions.ObjectPool.csproj @@ -2,7 +2,6 @@ netstandard2.0 - false diff --git a/src/WebEncoders/ref/Microsoft.Extensions.WebEncoders.csproj b/src/WebEncoders/ref/Microsoft.Extensions.WebEncoders.csproj index d102a7d31b..9dea660cb0 100644 --- a/src/WebEncoders/ref/Microsoft.Extensions.WebEncoders.csproj +++ b/src/WebEncoders/ref/Microsoft.Extensions.WebEncoders.csproj @@ -2,7 +2,6 @@ netstandard2.0 - false From b9912fee6b8082459fc33788c50916316944a2f2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?G=C3=BCnther=20Foidl?= Date: Sat, 19 Jan 2019 20:00:52 +0100 Subject: [PATCH 0060/1101] DisposableObjectPool added \n\nCommit migrated from https://github.com/dotnet/extensions/commit/6bcb1aee83d64225b24416b7c5e8d78f204ae2f0 --- src/ObjectPool/src/DefaultObjectPool.cs | 30 +++++-- src/ObjectPool/src/DisposableObjectPool.cs | 70 ++++++++++++++++ .../test/DisposableObjectPoolTest.cs | 83 +++++++++++++++++++ 3 files changed, 175 insertions(+), 8 deletions(-) create mode 100644 src/ObjectPool/src/DisposableObjectPool.cs create mode 100644 src/ObjectPool/test/DisposableObjectPoolTest.cs diff --git a/src/ObjectPool/src/DefaultObjectPool.cs b/src/ObjectPool/src/DefaultObjectPool.cs index dcd7f1c715..6ecc923140 100644 --- a/src/ObjectPool/src/DefaultObjectPool.cs +++ b/src/ObjectPool/src/DefaultObjectPool.cs @@ -10,10 +10,10 @@ namespace Microsoft.Extensions.ObjectPool { public class DefaultObjectPool : ObjectPool where T : class { - private readonly ObjectWrapper[] _items; + protected readonly ObjectWrapper[] _items; private readonly IPooledObjectPolicy _policy; private readonly bool _isDefaultPolicy; - private T _firstItem; + protected T _firstItem; // This class was introduced in 2.1 to avoid the interface call where possible private readonly PooledObjectPolicy _fastPolicy; @@ -71,29 +71,43 @@ namespace Microsoft.Extensions.ObjectPool [MethodImpl(MethodImplOptions.NoInlining)] private T Create() => _fastPolicy?.Create() ?? _policy.Create(); - public override void Return(T obj) + public override void Return(T obj) => ReturnCore(obj); + + protected bool ReturnCore(T obj) { if (_isDefaultPolicy || (_fastPolicy?.Return(obj) ?? _policy.Return(obj))) { - if (_firstItem != null || Interlocked.CompareExchange(ref _firstItem, obj, null) != null) + if (_firstItem == null && Interlocked.CompareExchange(ref _firstItem, obj, null) == null) { - ReturnViaScan(obj); + return true; // returned to pool + } + else + { + return ReturnViaScan(obj); } } + + return false; // not returned to pool } [MethodImpl(MethodImplOptions.AggressiveInlining)] - private void ReturnViaScan(T obj) + private bool ReturnViaScan(T obj) { var items = _items; - for (var i = 0; i < items.Length && Interlocked.CompareExchange(ref items[i].Element, obj, null) != null; ++i) + for (var i = 0; i < items.Length; i++) { + if (Interlocked.CompareExchange(ref items[i].Element, obj, null) == null) + { + return true; + } } + + return false; } // PERF: the struct wrapper avoids array-covariance-checks from the runtime when assigning to elements of the array. [DebuggerDisplay("{Element}")] - private struct ObjectWrapper + protected struct ObjectWrapper { public T Element; } diff --git a/src/ObjectPool/src/DisposableObjectPool.cs b/src/ObjectPool/src/DisposableObjectPool.cs new file mode 100644 index 0000000000..ca9288a6d6 --- /dev/null +++ b/src/ObjectPool/src/DisposableObjectPool.cs @@ -0,0 +1,70 @@ +// 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.Threading; + +namespace Microsoft.Extensions.ObjectPool +{ + public class DisposableObjectPool : DefaultObjectPool, IDisposable where T : class + { + private volatile bool _isDisposed; + + public DisposableObjectPool(IPooledObjectPolicy policy) + : base(policy) + { + } + + public DisposableObjectPool(IPooledObjectPolicy policy, int maximumRetained) + : base(policy, maximumRetained) + { + } + + public override T Get() + { + if (_isDisposed) + { + ThrowObjectDisposedException(); + } + + return base.Get(); + + void ThrowObjectDisposedException() + { + throw new ObjectDisposedException(this.GetType().Name); + } + } + + public override void Return(T obj) + { + // When the pool is disposed or the obj is not returned to the pool, dispose it + if (_isDisposed || !ReturnCore(obj)) + { + DisposeItem(obj); + } + } + + public void Dispose() + { + DisposeItem(_firstItem); + _firstItem = null; + + ObjectWrapper[] items = _items; + for (var i = 0; i < items.Length; i++) + { + DisposeItem(items[i].Element); + items[i].Element = null; + } + + _isDisposed = true; + } + + private void DisposeItem(T item) + { + if (item is IDisposable disposable) + { + disposable.Dispose(); + } + } + } +} diff --git a/src/ObjectPool/test/DisposableObjectPoolTest.cs b/src/ObjectPool/test/DisposableObjectPoolTest.cs new file mode 100644 index 0000000000..4bc9142efb --- /dev/null +++ b/src/ObjectPool/test/DisposableObjectPoolTest.cs @@ -0,0 +1,83 @@ +// 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 Xunit; + +namespace Microsoft.Extensions.ObjectPool +{ + public class DisposableObjectPoolTest + { + [Fact] + public void DisposableObjectPoolWithOneElement_Dispose_ObjectDisposed() + { + // Arrange + var pool = new DisposableObjectPool(new DefaultPooledObjectPolicy()); + var obj = pool.Get(); + pool.Return(obj); + + // Act + pool.Dispose(); + + // Assert + Assert.True(obj.IsDisposed); + } + + [Fact] + public void DisposableObjectPoolWithTwoElements_Dispose_ObjectsDisposed() + { + // Arrange + var pool = new DisposableObjectPool(new DefaultPooledObjectPolicy()); + var obj1 = pool.Get(); + var obj2 = pool.Get(); + pool.Return(obj1); + pool.Return(obj2); + + // Act + pool.Dispose(); + + // Assert + Assert.True(obj1.IsDisposed); + Assert.True(obj2.IsDisposed); + } + + [Fact] + public void DisposableObjectPool_DisposeAndGet_ThrowsObjectDisposed() + { + // Arrange + var pool = new DisposableObjectPool(new DefaultPooledObjectPolicy()); + var obj1 = pool.Get(); + var obj2 = pool.Get(); + pool.Return(obj1); + pool.Return(obj2); + + // Act + pool.Dispose(); + + // Assert + Assert.Throws(() => pool.Get()); + } + + [Fact] + public void DisposableObjectPool_DisposeAndReturn_DisposesObject() + { + // Arrange + var pool = new DisposableObjectPool(new DefaultPooledObjectPolicy()); + var obj = pool.Get(); + + // Act + pool.Dispose(); + pool.Return(obj); + + // Assert + Assert.True(obj.IsDisposed); + } + + private class DisposableObject : IDisposable + { + public bool IsDisposed { get; private set; } + + public void Dispose() => IsDisposed = true; + } + } +} From 033b118a9623873fddb3b8a6beac7ef03462d695 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?G=C3=BCnther=20Foidl?= Date: Sat, 19 Jan 2019 20:16:41 +0100 Subject: [PATCH 0061/1101] DefaultObjectPoolProvider chooses on T and added factory method to ObjectPool \n\nCommit migrated from https://github.com/dotnet/extensions/commit/17883d97a9fc1d8bd5e42a126f70338458dd95b8 --- .../src/DefaultObjectPoolProvider.cs | 5 +++ src/ObjectPool/src/ObjectPool.cs | 9 ++++ .../test/DefaultObjectPoolProviderTest.cs | 44 +++++++++++++++++++ 3 files changed, 58 insertions(+) create mode 100644 src/ObjectPool/test/DefaultObjectPoolProviderTest.cs diff --git a/src/ObjectPool/src/DefaultObjectPoolProvider.cs b/src/ObjectPool/src/DefaultObjectPoolProvider.cs index fb3c4bfa7e..cedacaedbd 100644 --- a/src/ObjectPool/src/DefaultObjectPoolProvider.cs +++ b/src/ObjectPool/src/DefaultObjectPoolProvider.cs @@ -11,6 +11,11 @@ namespace Microsoft.Extensions.ObjectPool public override ObjectPool Create(IPooledObjectPolicy policy) { + if (typeof(IDisposable).IsAssignableFrom(typeof(T))) + { + return new DisposableObjectPool(policy, MaximumRetained); + } + return new DefaultObjectPool(policy, MaximumRetained); } } diff --git a/src/ObjectPool/src/ObjectPool.cs b/src/ObjectPool/src/ObjectPool.cs index 8cf52c9195..691beae60c 100644 --- a/src/ObjectPool/src/ObjectPool.cs +++ b/src/ObjectPool/src/ObjectPool.cs @@ -9,4 +9,13 @@ namespace Microsoft.Extensions.ObjectPool public abstract void Return(T obj); } + + public static class ObjectPool + { + public static ObjectPool Create(IPooledObjectPolicy policy = null) where T : class, new() + { + var provider = new DefaultObjectPoolProvider(); + return provider.Create(policy ?? new DefaultPooledObjectPolicy()); + } + } } diff --git a/src/ObjectPool/test/DefaultObjectPoolProviderTest.cs b/src/ObjectPool/test/DefaultObjectPoolProviderTest.cs new file mode 100644 index 0000000000..7096b60b34 --- /dev/null +++ b/src/ObjectPool/test/DefaultObjectPoolProviderTest.cs @@ -0,0 +1,44 @@ +// 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 Xunit; + +namespace Microsoft.Extensions.ObjectPool +{ + public class DefaultObjectPoolProviderTest + { + [Fact] + public void DefaultObjectPoolProvider_CreateForObject_DefaultObjectPoolReturned() + { + // Arrange + var provider = new DefaultObjectPoolProvider(); + + // Act + var pool = provider.Create(); + + // Assert + Assert.IsType>(pool); + } + + [Fact] + public void DefaultObjectPoolProvider_CreateForIDisposable_DisposableObjectPoolReturned() + { + // Arrange + var provider = new DefaultObjectPoolProvider(); + + // Act + var pool = provider.Create(); + + // Assert + Assert.IsType>(pool); + } + + private class DisposableObject : IDisposable + { + public bool IsDisposed { get; private set; } + + public void Dispose() => IsDisposed = true; + } + } +} From 5e19936a9f1a1e6d46878753bc7a8ca14001e125 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?G=C3=BCnther=20Foidl?= Date: Sun, 20 Jan 2019 21:01:59 +0100 Subject: [PATCH 0062/1101] PR Feedback * DefaultObjectPool left unchanged (expect access modifiers) + manual inlining ob scan-methods * DisposableObjectPool made internal and it handles Return self, thus keeping the DefaultObjectPool fast \n\nCommit migrated from https://github.com/dotnet/extensions/commit/a7dc496507e10fbf56e4b76a156b4d0a0f22bcba --- src/ObjectPool/src/DefaultObjectPool.cs | 72 ++++++------------- .../src/DefaultObjectPoolProvider.cs | 5 ++ src/ObjectPool/src/DisposableObjectPool.cs | 31 ++++++-- src/ObjectPool/src/Properties/AssemblyInfo.cs | 3 + .../test/DisposableObjectPoolTest.cs | 62 ++++++++++++++++ src/ObjectPool/test/ThreadingTest.cs | 18 ++++- 6 files changed, 135 insertions(+), 56 deletions(-) create mode 100644 src/ObjectPool/src/Properties/AssemblyInfo.cs diff --git a/src/ObjectPool/src/DefaultObjectPool.cs b/src/ObjectPool/src/DefaultObjectPool.cs index 6ecc923140..070508a014 100644 --- a/src/ObjectPool/src/DefaultObjectPool.cs +++ b/src/ObjectPool/src/DefaultObjectPool.cs @@ -10,13 +10,13 @@ namespace Microsoft.Extensions.ObjectPool { public class DefaultObjectPool : ObjectPool where T : class { - protected readonly ObjectWrapper[] _items; - private readonly IPooledObjectPolicy _policy; - private readonly bool _isDefaultPolicy; - protected T _firstItem; + private protected readonly ObjectWrapper[] _items; + private protected readonly IPooledObjectPolicy _policy; + private protected readonly bool _isDefaultPolicy; + private protected T _firstItem; // This class was introduced in 2.1 to avoid the interface call where possible - private readonly PooledObjectPolicy _fastPolicy; + private protected readonly PooledObjectPolicy _fastPolicy; public DefaultObjectPool(IPooledObjectPolicy policy) : this(policy, Environment.ProcessorCount * 2) @@ -45,69 +45,43 @@ namespace Microsoft.Extensions.ObjectPool var item = _firstItem; if (item == null || Interlocked.CompareExchange(ref _firstItem, null, item) != item) { - item = GetViaScan(); + var items = _items; + for (var i = 0; i < items.Length; i++) + { + item = items[i].Element; + if (item != null && Interlocked.CompareExchange(ref items[i].Element, null, item) == item) + { + return item; + } + } + + item = Create(); } return item; } - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private T GetViaScan() - { - var items = _items; - for (var i = 0; i < items.Length; i++) - { - var item = items[i].Element; - if (item != null && Interlocked.CompareExchange(ref items[i].Element, null, item) == item) - { - return item; - } - } - - return Create(); - } - // Non-inline to improve its code quality as uncommon path [MethodImpl(MethodImplOptions.NoInlining)] private T Create() => _fastPolicy?.Create() ?? _policy.Create(); - public override void Return(T obj) => ReturnCore(obj); - - protected bool ReturnCore(T obj) + public override void Return(T obj) { if (_isDefaultPolicy || (_fastPolicy?.Return(obj) ?? _policy.Return(obj))) { - if (_firstItem == null && Interlocked.CompareExchange(ref _firstItem, obj, null) == null) + if (_firstItem != null || Interlocked.CompareExchange(ref _firstItem, obj, null) != null) { - return true; // returned to pool - } - else - { - return ReturnViaScan(obj); + var items = _items; + for (var i = 0; i < items.Length && Interlocked.CompareExchange(ref items[i].Element, obj, null) != null; ++i) + { + } } } - - return false; // not returned to pool - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private bool ReturnViaScan(T obj) - { - var items = _items; - for (var i = 0; i < items.Length; i++) - { - if (Interlocked.CompareExchange(ref items[i].Element, obj, null) == null) - { - return true; - } - } - - return false; } // PERF: the struct wrapper avoids array-covariance-checks from the runtime when assigning to elements of the array. [DebuggerDisplay("{Element}")] - protected struct ObjectWrapper + private protected struct ObjectWrapper { public T Element; } diff --git a/src/ObjectPool/src/DefaultObjectPoolProvider.cs b/src/ObjectPool/src/DefaultObjectPoolProvider.cs index cedacaedbd..2e7767ab35 100644 --- a/src/ObjectPool/src/DefaultObjectPoolProvider.cs +++ b/src/ObjectPool/src/DefaultObjectPoolProvider.cs @@ -11,6 +11,11 @@ namespace Microsoft.Extensions.ObjectPool public override ObjectPool Create(IPooledObjectPolicy policy) { + if (policy == null) + { + throw new ArgumentNullException(nameof(policy)); + } + if (typeof(IDisposable).IsAssignableFrom(typeof(T))) { return new DisposableObjectPool(policy, MaximumRetained); diff --git a/src/ObjectPool/src/DisposableObjectPool.cs b/src/ObjectPool/src/DisposableObjectPool.cs index ca9288a6d6..17ada443e5 100644 --- a/src/ObjectPool/src/DisposableObjectPool.cs +++ b/src/ObjectPool/src/DisposableObjectPool.cs @@ -2,11 +2,12 @@ // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; +using System.Runtime.CompilerServices; using System.Threading; namespace Microsoft.Extensions.ObjectPool { - public class DisposableObjectPool : DefaultObjectPool, IDisposable where T : class + internal sealed class DisposableObjectPool : DefaultObjectPool, IDisposable where T : class { private volatile bool _isDisposed; @@ -31,7 +32,7 @@ namespace Microsoft.Extensions.ObjectPool void ThrowObjectDisposedException() { - throw new ObjectDisposedException(this.GetType().Name); + throw new ObjectDisposedException(GetType().Name); } } @@ -44,8 +45,32 @@ namespace Microsoft.Extensions.ObjectPool } } + private bool ReturnCore(T obj) + { + bool returnedTooPool = false; + + if (_isDefaultPolicy || (_fastPolicy?.Return(obj) ?? _policy.Return(obj))) + { + if (_firstItem == null && Interlocked.CompareExchange(ref _firstItem, obj, null) == null) + { + returnedTooPool = true; + } + else + { + var items = _items; + for (var i = 0; i < items.Length && !(returnedTooPool = Interlocked.CompareExchange(ref items[i].Element, obj, null) == null); i++) + { + } + } + } + + return returnedTooPool; + } + public void Dispose() { + _isDisposed = true; + DisposeItem(_firstItem); _firstItem = null; @@ -55,8 +80,6 @@ namespace Microsoft.Extensions.ObjectPool DisposeItem(items[i].Element); items[i].Element = null; } - - _isDisposed = true; } private void DisposeItem(T item) diff --git a/src/ObjectPool/src/Properties/AssemblyInfo.cs b/src/ObjectPool/src/Properties/AssemblyInfo.cs new file mode 100644 index 0000000000..121f8990b1 --- /dev/null +++ b/src/ObjectPool/src/Properties/AssemblyInfo.cs @@ -0,0 +1,3 @@ +using System.Runtime.CompilerServices; + +[assembly: InternalsVisibleTo("Microsoft.Extensions.ObjectPool.Tests, PublicKey=0024000004800000940000000602000000240000525341310004000001000100f33a29044fa9d740c9b3213a93e57c84b472c84e0b8a0e1ae48e67a9f8f6de9d5f7f3d52ac23e48ac51801f1dc950abe901da34d2a9e3baadb141a17c77ef3c565dd5ee5054b91cf63bb3c6ab83f72ab3aafe93d0fc3c2348b764fafb0b1c0733de51459aeab46580384bf9d74c4e28164b7cde247f891ba07891c9d872ad2bb")] diff --git a/src/ObjectPool/test/DisposableObjectPoolTest.cs b/src/ObjectPool/test/DisposableObjectPoolTest.cs index 4bc9142efb..3bcefbaf66 100644 --- a/src/ObjectPool/test/DisposableObjectPoolTest.cs +++ b/src/ObjectPool/test/DisposableObjectPoolTest.cs @@ -2,12 +2,61 @@ // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; +using System.Collections.Generic; using Xunit; namespace Microsoft.Extensions.ObjectPool { public class DisposableObjectPoolTest { + [Fact] + public void DisposableObjectPoolWithDefaultPolicy_GetAnd_ReturnObject_SameInstance() + { + // Arrange + var pool = new DisposableObjectPool(new DefaultPooledObjectPolicy()); + + var obj1 = pool.Get(); + pool.Return(obj1); + + // Act + var obj2 = pool.Get(); + + // Assert + Assert.Same(obj1, obj2); + } + + [Fact] + public void DisposableObjectPool_GetAndReturnObject_SameInstance() + { + // Arrange + var pool = new DisposableObjectPool>(new ListPolicy()); + + var list1 = pool.Get(); + pool.Return(list1); + + // Act + var list2 = pool.Get(); + + // Assert + Assert.Same(list1, list2); + } + + [Fact] + public void DisposableObjectPool_Return_RejectedByPolicy() + { + // Arrange + var pool = new DisposableObjectPool>(new ListPolicy()); + var list1 = pool.Get(); + list1.Capacity = 20; + + // Act + pool.Return(list1); + var list2 = pool.Get(); + + // Assert + Assert.NotSame(list1, list2); + } + [Fact] public void DisposableObjectPoolWithOneElement_Dispose_ObjectDisposed() { @@ -73,6 +122,19 @@ namespace Microsoft.Extensions.ObjectPool Assert.True(obj.IsDisposed); } + private class ListPolicy : IPooledObjectPolicy> + { + public List Create() + { + return new List(17); + } + + public bool Return(List obj) + { + return obj.Capacity == 17; + } + } + private class DisposableObject : IDisposable { public bool IsDisposed { get; private set; } diff --git a/src/ObjectPool/test/ThreadingTest.cs b/src/ObjectPool/test/ThreadingTest.cs index 541bc5ffd4..dbab7a5301 100644 --- a/src/ObjectPool/test/ThreadingTest.cs +++ b/src/ObjectPool/test/ThreadingTest.cs @@ -13,10 +13,22 @@ namespace Microsoft.Extensions.ObjectPool private bool _foundError; [Fact] - public void RunThreadingTest() + public void DefaultObjectPool_RunThreadingTest() + { + _pool = new DefaultObjectPool(new DefaultPooledObjectPolicy(), 10); + RunThreadingTest(); + } + + [Fact] + public void DisposableObjectPool_RunThreadingTest() + { + _pool = new DisposableObjectPool(new DefaultPooledObjectPolicy(), 10); + RunThreadingTest(); + } + + private void RunThreadingTest() { _cts = new CancellationTokenSource(); - _pool = new DefaultObjectPool(new DefaultPooledObjectPolicy(), 10); var threads = new Thread[8]; for (var i = 0; i < threads.Length; i++) @@ -66,7 +78,7 @@ namespace Microsoft.Extensions.ObjectPool _pool.Return(obj2); } } - + private class Item { public int i = 0; From 491cbdd1194b8006a611272f1e9df7f56461e9d8 Mon Sep 17 00:00:00 2001 From: Ryan Nowak Date: Sun, 17 Feb 2019 12:44:35 -0800 Subject: [PATCH 0063/1101] Update ObjectPool reference assemblies \n\nCommit migrated from https://github.com/dotnet/extensions/commit/f13113c06bea25f2d9ab6daae6f5b12ebaf74cdf --- .../ref/Microsoft.Extensions.ObjectPool.netstandard2.0.cs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/ObjectPool/ref/Microsoft.Extensions.ObjectPool.netstandard2.0.cs b/src/ObjectPool/ref/Microsoft.Extensions.ObjectPool.netstandard2.0.cs index 16fd8379a6..083aaf14ef 100644 --- a/src/ObjectPool/ref/Microsoft.Extensions.ObjectPool.netstandard2.0.cs +++ b/src/ObjectPool/ref/Microsoft.Extensions.ObjectPool.netstandard2.0.cs @@ -38,6 +38,10 @@ namespace Microsoft.Extensions.ObjectPool public override T Get() { throw null; } public override void Return(T obj) { } } + public static partial class ObjectPool + { + public static Microsoft.Extensions.ObjectPool.ObjectPool Create(Microsoft.Extensions.ObjectPool.IPooledObjectPolicy policy = null) where T : class, new() { throw null; } + } public abstract partial class ObjectPoolProvider { protected ObjectPoolProvider() { } From a4cd6152b674aabcfecf392ff3e1bcb580b7163b Mon Sep 17 00:00:00 2001 From: Pranav K Date: Tue, 19 Feb 2019 13:01:13 -0800 Subject: [PATCH 0064/1101] Make JSRuntime.Current non-public (dotnet/extensions#1118) Fixes https://github.com/aspnet/AspNetCore/issues/6828\n\nCommit migrated from https://github.com/dotnet/extensions/commit/c1d8be8b980de62a299958e1816f06a81fea07bb --- .../ref/Microsoft.JSInterop.netstandard2.0.cs | 1 - src/JSInterop/Microsoft.JSInterop/src/JSRuntime.cs | 8 ++------ 2 files changed, 2 insertions(+), 7 deletions(-) diff --git a/src/JSInterop/Microsoft.JSInterop/ref/Microsoft.JSInterop.netstandard2.0.cs b/src/JSInterop/Microsoft.JSInterop/ref/Microsoft.JSInterop.netstandard2.0.cs index 02146c7bab..95b9b7956c 100644 --- a/src/JSInterop/Microsoft.JSInterop/ref/Microsoft.JSInterop.netstandard2.0.cs +++ b/src/JSInterop/Microsoft.JSInterop/ref/Microsoft.JSInterop.netstandard2.0.cs @@ -52,7 +52,6 @@ namespace Microsoft.JSInterop } public static partial class JSRuntime { - public static Microsoft.JSInterop.IJSRuntime Current { get { throw null; } } public static void SetCurrentJSRuntime(Microsoft.JSInterop.IJSRuntime instance) { } } public abstract partial class JSRuntimeBase : Microsoft.JSInterop.IJSRuntime diff --git a/src/JSInterop/Microsoft.JSInterop/src/JSRuntime.cs b/src/JSInterop/Microsoft.JSInterop/src/JSRuntime.cs index 5a9830fa35..ae097ca68e 100644 --- a/src/JSInterop/Microsoft.JSInterop/src/JSRuntime.cs +++ b/src/JSInterop/Microsoft.JSInterop/src/JSRuntime.cs @@ -11,13 +11,9 @@ namespace Microsoft.JSInterop /// public static class JSRuntime { - private static AsyncLocal _currentJSRuntime - = new AsyncLocal(); + private static readonly AsyncLocal _currentJSRuntime = new AsyncLocal(); - /// - /// Gets the current , if any. - /// - public static IJSRuntime Current => _currentJSRuntime.Value; + internal static IJSRuntime Current => _currentJSRuntime.Value; /// /// Sets the current JS runtime to the supplied instance. From ffd47e1bd84cc405de8a0d4eacc398840a07678c Mon Sep 17 00:00:00 2001 From: Pavel Krymets Date: Tue, 19 Feb 2019 17:25:53 -0800 Subject: [PATCH 0065/1101] Manualy update tooling dependencies to normalize names (dotnet/extensions#1135) * Manualy update tooling dependencies to normalize names * Nullable \n\nCommit migrated from https://github.com/dotnet/extensions/commit/77db8d2b6bcf2d3b655a1d4852febf0fb47cca82 --- ...iagnostics.HealthChecks.Abstractions.netstandard2.0.cs | 4 ++-- ....Extensions.Diagnostics.HealthChecks.netstandard2.0.cs | 8 ++++---- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/HealthChecks/Abstractions/ref/Microsoft.Extensions.Diagnostics.HealthChecks.Abstractions.netstandard2.0.cs b/src/HealthChecks/Abstractions/ref/Microsoft.Extensions.Diagnostics.HealthChecks.Abstractions.netstandard2.0.cs index c0117ef473..7fb5beb0e9 100644 --- a/src/HealthChecks/Abstractions/ref/Microsoft.Extensions.Diagnostics.HealthChecks.Abstractions.netstandard2.0.cs +++ b/src/HealthChecks/Abstractions/ref/Microsoft.Extensions.Diagnostics.HealthChecks.Abstractions.netstandard2.0.cs @@ -10,8 +10,8 @@ namespace Microsoft.Extensions.Diagnostics.HealthChecks } public sealed partial class HealthCheckRegistration { - public HealthCheckRegistration(string name, Microsoft.Extensions.Diagnostics.HealthChecks.IHealthCheck instance, System.Nullable failureStatus, System.Collections.Generic.IEnumerable tags) { } - public HealthCheckRegistration(string name, System.Func factory, System.Nullable failureStatus, System.Collections.Generic.IEnumerable tags) { } + public HealthCheckRegistration(string name, Microsoft.Extensions.Diagnostics.HealthChecks.IHealthCheck instance, Microsoft.Extensions.Diagnostics.HealthChecks.HealthStatus? failureStatus, System.Collections.Generic.IEnumerable tags) { } + public HealthCheckRegistration(string name, System.Func factory, Microsoft.Extensions.Diagnostics.HealthChecks.HealthStatus? failureStatus, System.Collections.Generic.IEnumerable tags) { } public System.Func Factory { get { throw null; } set { } } public Microsoft.Extensions.Diagnostics.HealthChecks.HealthStatus FailureStatus { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } public string Name { get { throw null; } set { } } diff --git a/src/HealthChecks/HealthChecks/ref/Microsoft.Extensions.Diagnostics.HealthChecks.netstandard2.0.cs b/src/HealthChecks/HealthChecks/ref/Microsoft.Extensions.Diagnostics.HealthChecks.netstandard2.0.cs index 89281584e2..58bdc80602 100644 --- a/src/HealthChecks/HealthChecks/ref/Microsoft.Extensions.Diagnostics.HealthChecks.netstandard2.0.cs +++ b/src/HealthChecks/HealthChecks/ref/Microsoft.Extensions.Diagnostics.HealthChecks.netstandard2.0.cs @@ -5,10 +5,10 @@ namespace Microsoft.Extensions.DependencyInjection { public static partial class HealthChecksBuilderAddCheckExtensions { - public static Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder AddCheck(this Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder builder, string name, Microsoft.Extensions.Diagnostics.HealthChecks.IHealthCheck instance, System.Nullable failureStatus = default(System.Nullable), System.Collections.Generic.IEnumerable tags = null) { throw null; } - public static Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder AddCheck(this Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder builder, string name, System.Nullable failureStatus = default(System.Nullable), System.Collections.Generic.IEnumerable tags = null) where T : class, Microsoft.Extensions.Diagnostics.HealthChecks.IHealthCheck { throw null; } - public static Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder AddTypeActivatedCheck(this Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder builder, string name, System.Nullable failureStatus, System.Collections.Generic.IEnumerable tags, params object[] args) where T : class, Microsoft.Extensions.Diagnostics.HealthChecks.IHealthCheck { throw null; } - public static Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder AddTypeActivatedCheck(this Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder builder, string name, System.Nullable failureStatus, params object[] args) where T : class, Microsoft.Extensions.Diagnostics.HealthChecks.IHealthCheck { throw null; } + public static Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder AddCheck(this Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder builder, string name, Microsoft.Extensions.Diagnostics.HealthChecks.IHealthCheck instance, Microsoft.Extensions.Diagnostics.HealthChecks.HealthStatus? failureStatus = default(Microsoft.Extensions.Diagnostics.HealthChecks.HealthStatus?), System.Collections.Generic.IEnumerable tags = null) { throw null; } + public static Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder AddCheck(this Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder builder, string name, Microsoft.Extensions.Diagnostics.HealthChecks.HealthStatus? failureStatus = default(Microsoft.Extensions.Diagnostics.HealthChecks.HealthStatus?), System.Collections.Generic.IEnumerable tags = null) where T : class, Microsoft.Extensions.Diagnostics.HealthChecks.IHealthCheck { throw null; } + public static Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder AddTypeActivatedCheck(this Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder builder, string name, Microsoft.Extensions.Diagnostics.HealthChecks.HealthStatus? failureStatus, System.Collections.Generic.IEnumerable tags, params object[] args) where T : class, Microsoft.Extensions.Diagnostics.HealthChecks.IHealthCheck { throw null; } + public static Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder AddTypeActivatedCheck(this Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder builder, string name, Microsoft.Extensions.Diagnostics.HealthChecks.HealthStatus? failureStatus, params object[] args) where T : class, Microsoft.Extensions.Diagnostics.HealthChecks.IHealthCheck { throw null; } public static Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder AddTypeActivatedCheck(this Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder builder, string name, params object[] args) where T : class, Microsoft.Extensions.Diagnostics.HealthChecks.IHealthCheck { throw null; } } public static partial class HealthChecksBuilderDelegateExtensions From 58ea57e63f33e15049676b62beb595d951b004d1 Mon Sep 17 00:00:00 2001 From: Ryan Brandenburg Date: Wed, 20 Feb 2019 11:43:55 -0800 Subject: [PATCH 0066/1101] Obsolete ResourceManagerWithCultureStringLocalizer and WithCulture (dotnet/extensions#1133) Obsolete ResourceManagerWithCultureStringLocalizer and WithCulture \n\nCommit migrated from https://github.com/dotnet/extensions/commit/924015e98bc443023a6b0eea2c0016b876e4051f --- ...Extensions.Localization.Abstractions.netstandard2.0.cs | 2 ++ src/Localization/Abstractions/src/IStringLocalizer.cs | 8 +++++--- src/Localization/Abstractions/src/StringLocalizerOfT.cs | 7 ++++--- .../Microsoft.Extensions.Localization.netstandard2.0.cs | 2 ++ .../Localization/src/ResourceManagerStringLocalizer.cs | 7 ++++--- .../src/ResourceManagerWithCultureStringLocalizer.cs | 7 ++++--- .../test/ResourceManagerStringLocalizerTest.cs | 5 ++--- 7 files changed, 23 insertions(+), 15 deletions(-) diff --git a/src/Localization/Abstractions/ref/Microsoft.Extensions.Localization.Abstractions.netstandard2.0.cs b/src/Localization/Abstractions/ref/Microsoft.Extensions.Localization.Abstractions.netstandard2.0.cs index b18cec35f8..3f9f422021 100644 --- a/src/Localization/Abstractions/ref/Microsoft.Extensions.Localization.Abstractions.netstandard2.0.cs +++ b/src/Localization/Abstractions/ref/Microsoft.Extensions.Localization.Abstractions.netstandard2.0.cs @@ -8,6 +8,7 @@ namespace Microsoft.Extensions.Localization Microsoft.Extensions.Localization.LocalizedString this[string name] { get; } Microsoft.Extensions.Localization.LocalizedString this[string name, params object[] arguments] { get; } System.Collections.Generic.IEnumerable GetAllStrings(bool includeParentCultures); + [System.ObsoleteAttribute("This method is obsolete. Use `CurrentCulture` and `CurrentUICulture` instead.")] Microsoft.Extensions.Localization.IStringLocalizer WithCulture(System.Globalization.CultureInfo culture); } public partial interface IStringLocalizerFactory @@ -42,6 +43,7 @@ namespace Microsoft.Extensions.Localization public virtual Microsoft.Extensions.Localization.LocalizedString this[string name] { get { throw null; } } public virtual Microsoft.Extensions.Localization.LocalizedString this[string name, params object[] arguments] { get { throw null; } } public System.Collections.Generic.IEnumerable GetAllStrings(bool includeParentCultures) { throw null; } + [System.ObsoleteAttribute("This method is obsolete. Use `CurrentCulture` and `CurrentUICulture` instead.")] public virtual Microsoft.Extensions.Localization.IStringLocalizer WithCulture(System.Globalization.CultureInfo culture) { throw null; } } } diff --git a/src/Localization/Abstractions/src/IStringLocalizer.cs b/src/Localization/Abstractions/src/IStringLocalizer.cs index 0e1145bbca..cabdf434ac 100644 --- a/src/Localization/Abstractions/src/IStringLocalizer.cs +++ b/src/Localization/Abstractions/src/IStringLocalizer.cs @@ -1,6 +1,7 @@ -// 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. +// 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.Collections.Generic; using System.Globalization; @@ -40,6 +41,7 @@ namespace Microsoft.Extensions.Localization /// /// The to use. /// A culture-specific . + [Obsolete("This method is obsolete. Use `CurrentCulture` and `CurrentUICulture` instead.")] IStringLocalizer WithCulture(CultureInfo culture); } -} \ No newline at end of file +} diff --git a/src/Localization/Abstractions/src/StringLocalizerOfT.cs b/src/Localization/Abstractions/src/StringLocalizerOfT.cs index 131c1126ec..4190ca14ff 100644 --- a/src/Localization/Abstractions/src/StringLocalizerOfT.cs +++ b/src/Localization/Abstractions/src/StringLocalizerOfT.cs @@ -1,5 +1,5 @@ -// 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. +// 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.Collections.Generic; @@ -30,6 +30,7 @@ namespace Microsoft.Extensions.Localization } /// + [Obsolete("This method is obsolete. Use `CurrentCulture` and `CurrentUICulture` instead.")] public virtual IStringLocalizer WithCulture(CultureInfo culture) => _localizer.WithCulture(culture); /// @@ -64,4 +65,4 @@ namespace Microsoft.Extensions.Localization public IEnumerable GetAllStrings(bool includeParentCultures) => _localizer.GetAllStrings(includeParentCultures); } -} \ No newline at end of file +} diff --git a/src/Localization/Localization/ref/Microsoft.Extensions.Localization.netstandard2.0.cs b/src/Localization/Localization/ref/Microsoft.Extensions.Localization.netstandard2.0.cs index 7bbe1797a5..80175da718 100644 --- a/src/Localization/Localization/ref/Microsoft.Extensions.Localization.netstandard2.0.cs +++ b/src/Localization/Localization/ref/Microsoft.Extensions.Localization.netstandard2.0.cs @@ -36,6 +36,7 @@ namespace Microsoft.Extensions.Localization public virtual System.Collections.Generic.IEnumerable GetAllStrings(bool includeParentCultures) { throw null; } protected System.Collections.Generic.IEnumerable GetAllStrings(bool includeParentCultures, System.Globalization.CultureInfo culture) { throw null; } protected string GetStringSafely(string name, System.Globalization.CultureInfo culture) { throw null; } + [System.ObsoleteAttribute("This method is obsolete. Use `CurrentCulture` and `CurrentUICulture` instead.")] public Microsoft.Extensions.Localization.IStringLocalizer WithCulture(System.Globalization.CultureInfo culture) { throw null; } } public partial class ResourceManagerStringLocalizerFactory : Microsoft.Extensions.Localization.IStringLocalizerFactory @@ -51,6 +52,7 @@ namespace Microsoft.Extensions.Localization protected virtual string GetResourcePrefix(string location, string baseName, string resourceLocation) { throw null; } protected virtual Microsoft.Extensions.Localization.RootNamespaceAttribute GetRootNamespaceAttribute(System.Reflection.Assembly assembly) { throw null; } } + [System.ObsoleteAttribute("This method is obsolete. Use `CurrentCulture` and `CurrentUICulture` instead.")] public partial class ResourceManagerWithCultureStringLocalizer : Microsoft.Extensions.Localization.ResourceManagerStringLocalizer { public ResourceManagerWithCultureStringLocalizer(System.Resources.ResourceManager resourceManager, System.Reflection.Assembly resourceAssembly, string baseName, Microsoft.Extensions.Localization.IResourceNamesCache resourceNamesCache, System.Globalization.CultureInfo culture, Microsoft.Extensions.Logging.ILogger logger) : base (default(System.Resources.ResourceManager), default(System.Reflection.Assembly), default(string), default(Microsoft.Extensions.Localization.IResourceNamesCache), default(Microsoft.Extensions.Logging.ILogger)) { } diff --git a/src/Localization/Localization/src/ResourceManagerStringLocalizer.cs b/src/Localization/Localization/src/ResourceManagerStringLocalizer.cs index e2e1a3f234..90f8e077b4 100644 --- a/src/Localization/Localization/src/ResourceManagerStringLocalizer.cs +++ b/src/Localization/Localization/src/ResourceManagerStringLocalizer.cs @@ -1,5 +1,5 @@ -// 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. +// 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.Collections.Concurrent; @@ -151,6 +151,7 @@ namespace Microsoft.Extensions.Localization /// /// The to use. /// A culture-specific . + [Obsolete("This method is obsolete. Use `CurrentCulture` and `CurrentUICulture` instead.")] public IStringLocalizer WithCulture(CultureInfo culture) { return culture == null @@ -271,4 +272,4 @@ namespace Microsoft.Extensions.Localization return resourceNames; } } -} \ No newline at end of file +} diff --git a/src/Localization/Localization/src/ResourceManagerWithCultureStringLocalizer.cs b/src/Localization/Localization/src/ResourceManagerWithCultureStringLocalizer.cs index 65b6ae242c..2bc51289da 100644 --- a/src/Localization/Localization/src/ResourceManagerWithCultureStringLocalizer.cs +++ b/src/Localization/Localization/src/ResourceManagerWithCultureStringLocalizer.cs @@ -1,5 +1,5 @@ -// 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. +// 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.Collections.Generic; @@ -15,6 +15,7 @@ namespace Microsoft.Extensions.Localization /// An that uses the and /// to provide localized strings for a specific . /// + [Obsolete("This method is obsolete. Use `CurrentCulture` and `CurrentUICulture` instead.")] public class ResourceManagerWithCultureStringLocalizer : ResourceManagerStringLocalizer { private readonly string _resourceBaseName; @@ -161,4 +162,4 @@ namespace Microsoft.Extensions.Localization public override IEnumerable GetAllStrings(bool includeParentCultures) => GetAllStrings(includeParentCultures, _culture); } -} \ No newline at end of file +} diff --git a/src/Localization/Localization/test/ResourceManagerStringLocalizerTest.cs b/src/Localization/Localization/test/ResourceManagerStringLocalizerTest.cs index ff7bfa9933..a82ce9d1d3 100644 --- a/src/Localization/Localization/test/ResourceManagerStringLocalizerTest.cs +++ b/src/Localization/Localization/test/ResourceManagerStringLocalizerTest.cs @@ -182,12 +182,11 @@ namespace Microsoft.Extensions.Localization var resourceManager = new TestResourceManager(baseName, resourceAssembly); var logger = Logger; - var localizer = new ResourceManagerWithCultureStringLocalizer( + var localizer = new ResourceManagerStringLocalizer( resourceManager, resourceAssembly.Assembly, baseName, resourceNamesCache, - CultureInfo.CurrentCulture, logger); // Act & Assert @@ -291,7 +290,7 @@ namespace Microsoft.Extensions.Localization public override Stream GetManifestResourceStream(string name) { ManifestResourceStreamCallCount++; - + return HasResources ? MakeResourceStream() : null; } } From d805cf3b2e5507940152361af106b74eb8043f3b Mon Sep 17 00:00:00 2001 From: Nathanael Marchand Date: Mon, 18 Feb 2019 15:48:45 +0100 Subject: [PATCH 0067/1101] Implement parallel health checks \n\nCommit migrated from https://github.com/dotnet/extensions/commit/f14a45e09670dc998be5750e66f363ca45e6931c --- .../src/DefaultHealthCheckService.cs | 137 +++++++++--------- 1 file changed, 71 insertions(+), 66 deletions(-) diff --git a/src/HealthChecks/HealthChecks/src/DefaultHealthCheckService.cs b/src/HealthChecks/HealthChecks/src/DefaultHealthCheckService.cs index d5d71d9cb4..b2b99c99b3 100644 --- a/src/HealthChecks/HealthChecks/src/DefaultHealthCheckService.cs +++ b/src/HealthChecks/HealthChecks/src/DefaultHealthCheckService.cs @@ -39,76 +39,81 @@ namespace Microsoft.Extensions.Diagnostics.HealthChecks Func predicate, CancellationToken cancellationToken = default) { - var registrations = _options.Value.Registrations; + async Task<(string registrationName, HealthReportEntry result)> RunCheckAsync(IServiceScope scope, HealthCheckRegistration registration) + { + cancellationToken.ThrowIfCancellationRequested(); + var healthCheck = registration.Factory(scope.ServiceProvider); + + // If the health check does things like make Database queries using EF or backend HTTP calls, + // it may be valuable to know that logs it generates are part of a health check. So we start a scope. + using (_logger.BeginScope(new HealthCheckLogScope(registration.Name))) + { + var stopwatch = ValueStopwatch.StartNew(); + var context = new HealthCheckContext { Registration = registration }; + + Log.HealthCheckBegin(_logger, registration); + + HealthReportEntry entry; + try + { + var result = await healthCheck.CheckHealthAsync(context, cancellationToken); + var duration = stopwatch.GetElapsedTime(); + + entry = new HealthReportEntry( + status: result.Status, + description: result.Description, + duration: duration, + exception: result.Exception, + data: result.Data); + + Log.HealthCheckEnd(_logger, registration, entry, duration); + Log.HealthCheckData(_logger, registration, entry); + } + + // Allow cancellation to propagate. + catch (Exception ex) when (ex as OperationCanceledException == null) + { + var duration = stopwatch.GetElapsedTime(); + entry = new HealthReportEntry( + status: HealthStatus.Unhealthy, + description: ex.Message, + duration: duration, + exception: ex, + data: null); + + Log.HealthCheckError(_logger, registration, ex, duration); + } + + return (registration.Name, entry); + } + } + + IEnumerable registrations = _options.Value.Registrations; + if (predicate != null) + { + registrations = registrations.Where(predicate); + } + + var totalTime = ValueStopwatch.StartNew(); + Log.HealthCheckProcessingBegin(_logger); + + (string registrationName, HealthReportEntry result)[] results; using (var scope = _scopeFactory.CreateScope()) { - var context = new HealthCheckContext(); - var entries = new Dictionary(StringComparer.OrdinalIgnoreCase); - - var totalTime = ValueStopwatch.StartNew(); - Log.HealthCheckProcessingBegin(_logger); - - foreach (var registration in registrations) - { - if (predicate != null && !predicate(registration)) - { - continue; - } - - cancellationToken.ThrowIfCancellationRequested(); - - var healthCheck = registration.Factory(scope.ServiceProvider); - - // If the health check does things like make Database queries using EF or backend HTTP calls, - // it may be valuable to know that logs it generates are part of a health check. So we start a scope. - using (_logger.BeginScope(new HealthCheckLogScope(registration.Name))) - { - var stopwatch = ValueStopwatch.StartNew(); - context.Registration = registration; - - Log.HealthCheckBegin(_logger, registration); - - HealthReportEntry entry; - try - { - var result = await healthCheck.CheckHealthAsync(context, cancellationToken); - var duration = stopwatch.GetElapsedTime(); - - entry = new HealthReportEntry( - status: result.Status, - description: result.Description, - duration: duration, - exception: result.Exception, - data: result.Data); - - Log.HealthCheckEnd(_logger, registration, entry, duration); - Log.HealthCheckData(_logger, registration, entry); - } - - // Allow cancellation to propagate. - catch (Exception ex) when (ex as OperationCanceledException == null) - { - var duration = stopwatch.GetElapsedTime(); - entry = new HealthReportEntry( - status: HealthStatus.Unhealthy, - description: ex.Message, - duration: duration, - exception: ex, - data: null); - - Log.HealthCheckError(_logger, registration, ex, duration); - } - - entries[registration.Name] = entry; - } - } - - var totalElapsedTime = totalTime.GetElapsedTime(); - var report = new HealthReport(entries, totalElapsedTime); - Log.HealthCheckProcessingEnd(_logger, report.Status, totalElapsedTime); - return report; + results = await Task.WhenAll(registrations.Select(r => RunCheckAsync(scope, r))); } + + var entries = new Dictionary(StringComparer.OrdinalIgnoreCase); + foreach (var (registrationName, result) in results) + { + entries[registrationName] = result; + } + + var totalElapsedTime = totalTime.GetElapsedTime(); + var report = new HealthReport(entries, totalElapsedTime); + Log.HealthCheckProcessingEnd(_logger, report.Status, totalElapsedTime); + return report; } private static void ValidateRegistrations(IEnumerable registrations) From 30c6051d3c1bc558f696d917816994200e2eee60 Mon Sep 17 00:00:00 2001 From: Nathanael Marchand Date: Wed, 20 Feb 2019 12:10:04 +0100 Subject: [PATCH 0068/1101] Take in account code review \n\nCommit migrated from https://github.com/dotnet/extensions/commit/ecea8c802e8dccee184c8cdaf251a5c8b9b80d57 --- .../src/DefaultHealthCheckService.cs | 121 ++++++++++-------- .../test/DefaultHealthCheckServiceTest.cs | 40 +++++- .../HealthCheckPublisherHostedServiceTest.cs | 12 +- 3 files changed, 110 insertions(+), 63 deletions(-) diff --git a/src/HealthChecks/HealthChecks/src/DefaultHealthCheckService.cs b/src/HealthChecks/HealthChecks/src/DefaultHealthCheckService.cs index b2b99c99b3..da8c00a5aa 100644 --- a/src/HealthChecks/HealthChecks/src/DefaultHealthCheckService.cs +++ b/src/HealthChecks/HealthChecks/src/DefaultHealthCheckService.cs @@ -39,75 +39,32 @@ namespace Microsoft.Extensions.Diagnostics.HealthChecks Func predicate, CancellationToken cancellationToken = default) { - async Task<(string registrationName, HealthReportEntry result)> RunCheckAsync(IServiceScope scope, HealthCheckRegistration registration) - { - cancellationToken.ThrowIfCancellationRequested(); - - var healthCheck = registration.Factory(scope.ServiceProvider); - - // If the health check does things like make Database queries using EF or backend HTTP calls, - // it may be valuable to know that logs it generates are part of a health check. So we start a scope. - using (_logger.BeginScope(new HealthCheckLogScope(registration.Name))) - { - var stopwatch = ValueStopwatch.StartNew(); - var context = new HealthCheckContext { Registration = registration }; - - Log.HealthCheckBegin(_logger, registration); - - HealthReportEntry entry; - try - { - var result = await healthCheck.CheckHealthAsync(context, cancellationToken); - var duration = stopwatch.GetElapsedTime(); - - entry = new HealthReportEntry( - status: result.Status, - description: result.Description, - duration: duration, - exception: result.Exception, - data: result.Data); - - Log.HealthCheckEnd(_logger, registration, entry, duration); - Log.HealthCheckData(_logger, registration, entry); - } - - // Allow cancellation to propagate. - catch (Exception ex) when (ex as OperationCanceledException == null) - { - var duration = stopwatch.GetElapsedTime(); - entry = new HealthReportEntry( - status: HealthStatus.Unhealthy, - description: ex.Message, - duration: duration, - exception: ex, - data: null); - - Log.HealthCheckError(_logger, registration, ex, duration); - } - - return (registration.Name, entry); - } - } - - IEnumerable registrations = _options.Value.Registrations; + var registrations = _options.Value.Registrations; if (predicate != null) { - registrations = registrations.Where(predicate); + registrations = registrations.Where(predicate).ToArray(); } var totalTime = ValueStopwatch.StartNew(); Log.HealthCheckProcessingBegin(_logger); - (string registrationName, HealthReportEntry result)[] results; + var tasks = new Task[registrations.Count]; + var index = 0; using (var scope = _scopeFactory.CreateScope()) { - results = await Task.WhenAll(registrations.Select(r => RunCheckAsync(scope, r))); + foreach (var registration in registrations) + { + tasks[index++] = RunCheckAsync(scope, registration, cancellationToken); + } + + await Task.WhenAll(tasks).ConfigureAwait(false); } + index = 0; var entries = new Dictionary(StringComparer.OrdinalIgnoreCase); - foreach (var (registrationName, result) in results) + foreach (var registration in registrations) { - entries[registrationName] = result; + entries[registration.Name] = tasks[index++].Result; } var totalElapsedTime = totalTime.GetElapsedTime(); @@ -116,6 +73,58 @@ namespace Microsoft.Extensions.Diagnostics.HealthChecks return report; } + private async Task RunCheckAsync(IServiceScope scope, HealthCheckRegistration registration, CancellationToken cancellationToken) + { + await Task.Yield(); + + cancellationToken.ThrowIfCancellationRequested(); + + var healthCheck = registration.Factory(scope.ServiceProvider); + + // If the health check does things like make Database queries using EF or backend HTTP calls, + // it may be valuable to know that logs it generates are part of a health check. So we start a scope. + using (_logger.BeginScope(new HealthCheckLogScope(registration.Name))) + { + var stopwatch = ValueStopwatch.StartNew(); + var context = new HealthCheckContext { Registration = registration }; + + Log.HealthCheckBegin(_logger, registration); + + HealthReportEntry entry; + try + { + var result = await healthCheck.CheckHealthAsync(context, cancellationToken); + var duration = stopwatch.GetElapsedTime(); + + entry = new HealthReportEntry( + status: result.Status, + description: result.Description, + duration: duration, + exception: result.Exception, + data: result.Data); + + Log.HealthCheckEnd(_logger, registration, entry, duration); + Log.HealthCheckData(_logger, registration, entry); + } + + // Allow cancellation to propagate. + catch (Exception ex) when (ex as OperationCanceledException == null) + { + var duration = stopwatch.GetElapsedTime(); + entry = new HealthReportEntry( + status: HealthStatus.Unhealthy, + description: ex.Message, + duration: duration, + exception: ex, + data: null); + + Log.HealthCheckError(_logger, registration, ex, duration); + } + + return entry; + } + } + private static void ValidateRegistrations(IEnumerable registrations) { // Scan the list for duplicate names to provide a better error if there are duplicates. diff --git a/src/HealthChecks/HealthChecks/test/DefaultHealthCheckServiceTest.cs b/src/HealthChecks/HealthChecks/test/DefaultHealthCheckServiceTest.cs index 9ab991204e..cc6d9936a2 100644 --- a/src/HealthChecks/HealthChecks/test/DefaultHealthCheckServiceTest.cs +++ b/src/HealthChecks/HealthChecks/test/DefaultHealthCheckServiceTest.cs @@ -375,12 +375,50 @@ namespace Microsoft.Extensions.Diagnostics.HealthChecks }); } - private static DefaultHealthCheckService CreateHealthChecksService(Action configure) + [Fact] + public async Task CheckHealthAsync_ChecksAreRunInParallel() + { + // Arrange + var sink = new TestSink(); + async Task CheckMethod() + { + await Task.Delay(100); + return HealthCheckResult.Healthy(); + } + var service = CreateHealthChecksService(b => + { + b.AddAsyncCheck("test1", CheckMethod); + b.AddAsyncCheck("test2", CheckMethod); + b.AddAsyncCheck("test3", CheckMethod); + }, sink); + + // Act + _ = await service.CheckHealthAsync(); + + // Assert + Assert.Collection( + sink.Writes, + entry => { Assert.Equal(DefaultHealthCheckService.EventIds.HealthCheckProcessingBegin, entry.EventId); }, + entry => { Assert.Equal(DefaultHealthCheckService.EventIds.HealthCheckBegin, entry.EventId); }, + entry => { Assert.Equal(DefaultHealthCheckService.EventIds.HealthCheckBegin, entry.EventId); }, + entry => { Assert.Equal(DefaultHealthCheckService.EventIds.HealthCheckBegin, entry.EventId); }, + entry => { Assert.Equal(DefaultHealthCheckService.EventIds.HealthCheckEnd, entry.EventId); }, + entry => { Assert.Equal(DefaultHealthCheckService.EventIds.HealthCheckEnd, entry.EventId); }, + entry => { Assert.Equal(DefaultHealthCheckService.EventIds.HealthCheckEnd, entry.EventId); }, + entry => { Assert.Equal(DefaultHealthCheckService.EventIds.HealthCheckProcessingEnd, entry.EventId); }); + } + + private static DefaultHealthCheckService CreateHealthChecksService(Action configure, ITestSink sink = null) { var services = new ServiceCollection(); services.AddLogging(); services.AddOptions(); + if (sink != null) + { + services.AddSingleton(new TestLoggerFactory(sink, enabled: true)); + } + var builder = services.AddHealthChecks(); if (configure != null) { diff --git a/src/HealthChecks/HealthChecks/test/HealthCheckPublisherHostedServiceTest.cs b/src/HealthChecks/HealthChecks/test/HealthCheckPublisherHostedServiceTest.cs index 94687efcb8..099944a473 100644 --- a/src/HealthChecks/HealthChecks/test/HealthCheckPublisherHostedServiceTest.cs +++ b/src/HealthChecks/HealthChecks/test/HealthCheckPublisherHostedServiceTest.cs @@ -211,8 +211,8 @@ namespace Microsoft.Extensions.Diagnostics.HealthChecks entry => { Assert.Equal(HealthCheckPublisherHostedService.EventIds.HealthCheckPublisherProcessingBegin, entry.EventId); }, entry => { Assert.Equal(DefaultHealthCheckService.EventIds.HealthCheckProcessingBegin, entry.EventId); }, entry => { Assert.Equal(DefaultHealthCheckService.EventIds.HealthCheckBegin, entry.EventId); }, - entry => { Assert.Equal(DefaultHealthCheckService.EventIds.HealthCheckEnd, entry.EventId); }, - entry => { Assert.Equal(DefaultHealthCheckService.EventIds.HealthCheckBegin, entry.EventId); }, + entry => { Assert.Contains(entry.EventId, new[] { DefaultHealthCheckService.EventIds.HealthCheckBegin, DefaultHealthCheckService.EventIds.HealthCheckEnd }); }, + entry => { Assert.Contains(entry.EventId, new[] { DefaultHealthCheckService.EventIds.HealthCheckBegin, DefaultHealthCheckService.EventIds.HealthCheckEnd }); }, entry => { Assert.Equal(DefaultHealthCheckService.EventIds.HealthCheckEnd, entry.EventId); }, entry => { Assert.Equal(DefaultHealthCheckService.EventIds.HealthCheckProcessingEnd, entry.EventId); }, entry => { Assert.Equal(HealthCheckPublisherHostedService.EventIds.HealthCheckPublisherBegin, entry.EventId); }, @@ -321,8 +321,8 @@ namespace Microsoft.Extensions.Diagnostics.HealthChecks entry => { Assert.Equal(HealthCheckPublisherHostedService.EventIds.HealthCheckPublisherProcessingBegin, entry.EventId); }, entry => { Assert.Equal(DefaultHealthCheckService.EventIds.HealthCheckProcessingBegin, entry.EventId); }, entry => { Assert.Equal(DefaultHealthCheckService.EventIds.HealthCheckBegin, entry.EventId); }, - entry => { Assert.Equal(DefaultHealthCheckService.EventIds.HealthCheckEnd, entry.EventId); }, - entry => { Assert.Equal(DefaultHealthCheckService.EventIds.HealthCheckBegin, entry.EventId); }, + entry => { Assert.Contains(entry.EventId, new[] { DefaultHealthCheckService.EventIds.HealthCheckBegin, DefaultHealthCheckService.EventIds.HealthCheckEnd }); }, + entry => { Assert.Contains(entry.EventId, new[] { DefaultHealthCheckService.EventIds.HealthCheckBegin, DefaultHealthCheckService.EventIds.HealthCheckEnd }); }, entry => { Assert.Equal(DefaultHealthCheckService.EventIds.HealthCheckEnd, entry.EventId); }, entry => { Assert.Equal(DefaultHealthCheckService.EventIds.HealthCheckProcessingEnd, entry.EventId); }, entry => { Assert.Equal(HealthCheckPublisherHostedService.EventIds.HealthCheckPublisherBegin, entry.EventId); }, @@ -399,8 +399,8 @@ namespace Microsoft.Extensions.Diagnostics.HealthChecks entry => { Assert.Equal(HealthCheckPublisherHostedService.EventIds.HealthCheckPublisherProcessingBegin, entry.EventId); }, entry => { Assert.Equal(DefaultHealthCheckService.EventIds.HealthCheckProcessingBegin, entry.EventId); }, entry => { Assert.Equal(DefaultHealthCheckService.EventIds.HealthCheckBegin, entry.EventId); }, - entry => { Assert.Equal(DefaultHealthCheckService.EventIds.HealthCheckEnd, entry.EventId); }, - entry => { Assert.Equal(DefaultHealthCheckService.EventIds.HealthCheckBegin, entry.EventId); }, + entry => { Assert.Contains(entry.EventId, new[] { DefaultHealthCheckService.EventIds.HealthCheckBegin, DefaultHealthCheckService.EventIds.HealthCheckEnd }); }, + entry => { Assert.Contains(entry.EventId, new[] { DefaultHealthCheckService.EventIds.HealthCheckBegin, DefaultHealthCheckService.EventIds.HealthCheckEnd }); }, entry => { Assert.Equal(DefaultHealthCheckService.EventIds.HealthCheckEnd, entry.EventId); }, entry => { Assert.Equal(DefaultHealthCheckService.EventIds.HealthCheckProcessingEnd, entry.EventId); }, entry => { Assert.Equal(HealthCheckPublisherHostedService.EventIds.HealthCheckPublisherBegin, entry.EventId); }, From 1f05a6666142453397c78dc72021a98f6f7f6a93 Mon Sep 17 00:00:00 2001 From: Nathanael Marchand Date: Thu, 21 Feb 2019 11:12:32 +0100 Subject: [PATCH 0069/1101] Take in account code review for test \n\nCommit migrated from https://github.com/dotnet/extensions/commit/49bf9920e4e9079b02e2f775b2bc923abf2be4d1 --- .../test/DefaultHealthCheckServiceTest.cs | 65 +++++++++++-------- 1 file changed, 38 insertions(+), 27 deletions(-) diff --git a/src/HealthChecks/HealthChecks/test/DefaultHealthCheckServiceTest.cs b/src/HealthChecks/HealthChecks/test/DefaultHealthCheckServiceTest.cs index cc6d9936a2..fa08cccd20 100644 --- a/src/HealthChecks/HealthChecks/test/DefaultHealthCheckServiceTest.cs +++ b/src/HealthChecks/HealthChecks/test/DefaultHealthCheckServiceTest.cs @@ -6,6 +6,7 @@ using System.Collections.Generic; using System.Linq; using System.Threading; using System.Threading.Tasks; +using Microsoft.AspNetCore.Testing; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging.Testing; @@ -379,46 +380,56 @@ namespace Microsoft.Extensions.Diagnostics.HealthChecks public async Task CheckHealthAsync_ChecksAreRunInParallel() { // Arrange - var sink = new TestSink(); - async Task CheckMethod() - { - await Task.Delay(100); - return HealthCheckResult.Healthy(); - } + var input1 = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); + var input2 = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); + var output1 = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); + var output2 = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); + var service = CreateHealthChecksService(b => { - b.AddAsyncCheck("test1", CheckMethod); - b.AddAsyncCheck("test2", CheckMethod); - b.AddAsyncCheck("test3", CheckMethod); - }, sink); + b.AddAsyncCheck("test1", + async () => + { + output1.SetResult(null); + await input1.Task; + return HealthCheckResult.Healthy(); + }); + b.AddAsyncCheck("test2", + async () => + { + output2.SetResult(null); + await input2.Task; + return HealthCheckResult.Healthy(); + }); + }); // Act - _ = await service.CheckHealthAsync(); + var checkHealthTask = service.CheckHealthAsync(); + await Task.WhenAll(output1.Task, output2.Task).TimeoutAfter(TimeSpan.FromSeconds(10)); + input1.SetResult(null); + input2.SetResult(null); + await checkHealthTask; // Assert - Assert.Collection( - sink.Writes, - entry => { Assert.Equal(DefaultHealthCheckService.EventIds.HealthCheckProcessingBegin, entry.EventId); }, - entry => { Assert.Equal(DefaultHealthCheckService.EventIds.HealthCheckBegin, entry.EventId); }, - entry => { Assert.Equal(DefaultHealthCheckService.EventIds.HealthCheckBegin, entry.EventId); }, - entry => { Assert.Equal(DefaultHealthCheckService.EventIds.HealthCheckBegin, entry.EventId); }, - entry => { Assert.Equal(DefaultHealthCheckService.EventIds.HealthCheckEnd, entry.EventId); }, - entry => { Assert.Equal(DefaultHealthCheckService.EventIds.HealthCheckEnd, entry.EventId); }, - entry => { Assert.Equal(DefaultHealthCheckService.EventIds.HealthCheckEnd, entry.EventId); }, - entry => { Assert.Equal(DefaultHealthCheckService.EventIds.HealthCheckProcessingEnd, entry.EventId); }); + Assert.Collection(checkHealthTask.Result.Entries, + entry => + { + Assert.Equal("test1", entry.Key); + Assert.Equal(HealthStatus.Healthy, entry.Value.Status); + }, + entry => + { + Assert.Equal("test2", entry.Key); + Assert.Equal(HealthStatus.Healthy, entry.Value.Status); + }); } - private static DefaultHealthCheckService CreateHealthChecksService(Action configure, ITestSink sink = null) + private static DefaultHealthCheckService CreateHealthChecksService(Action configure) { var services = new ServiceCollection(); services.AddLogging(); services.AddOptions(); - if (sink != null) - { - services.AddSingleton(new TestLoggerFactory(sink, enabled: true)); - } - var builder = services.AddHealthChecks(); if (configure != null) { From 7960bde7dada91a3c8bd51c971dcd6973d5aadde Mon Sep 17 00:00:00 2001 From: Nathanael Marchand Date: Tue, 19 Feb 2019 10:23:49 +0100 Subject: [PATCH 0070/1101] Implement Timeout \n\nCommit migrated from https://github.com/dotnet/extensions/commit/50593c9336a6746bcafa6325fc5ac772bb07fc65 --- ...ealthChecks.Abstractions.netstandard2.0.cs | 3 + .../src/HealthCheckRegistration.cs | 71 +++++++++++- ...Diagnostics.HealthChecks.netstandard2.0.cs | 19 +++- .../src/DefaultHealthCheckService.cs | 34 +++++- .../HealthChecksBuilderAddCheckExtensions.cs | 106 +++++++++++++++++- .../HealthChecksBuilderDelegateExtensions.cs | 98 ++++++++++++++-- .../test/DefaultHealthCheckServiceTest.cs | 26 +++++ 7 files changed, 332 insertions(+), 25 deletions(-) diff --git a/src/HealthChecks/Abstractions/ref/Microsoft.Extensions.Diagnostics.HealthChecks.Abstractions.netstandard2.0.cs b/src/HealthChecks/Abstractions/ref/Microsoft.Extensions.Diagnostics.HealthChecks.Abstractions.netstandard2.0.cs index 7fb5beb0e9..9ab497257e 100644 --- a/src/HealthChecks/Abstractions/ref/Microsoft.Extensions.Diagnostics.HealthChecks.Abstractions.netstandard2.0.cs +++ b/src/HealthChecks/Abstractions/ref/Microsoft.Extensions.Diagnostics.HealthChecks.Abstractions.netstandard2.0.cs @@ -11,11 +11,14 @@ namespace Microsoft.Extensions.Diagnostics.HealthChecks public sealed partial class HealthCheckRegistration { public HealthCheckRegistration(string name, Microsoft.Extensions.Diagnostics.HealthChecks.IHealthCheck instance, Microsoft.Extensions.Diagnostics.HealthChecks.HealthStatus? failureStatus, System.Collections.Generic.IEnumerable tags) { } + public HealthCheckRegistration(string name, Microsoft.Extensions.Diagnostics.HealthChecks.IHealthCheck instance, Microsoft.Extensions.Diagnostics.HealthChecks.HealthStatus? failureStatus, System.Collections.Generic.IEnumerable tags, System.TimeSpan? timeout) { } public HealthCheckRegistration(string name, System.Func factory, Microsoft.Extensions.Diagnostics.HealthChecks.HealthStatus? failureStatus, System.Collections.Generic.IEnumerable tags) { } + public HealthCheckRegistration(string name, System.Func factory, Microsoft.Extensions.Diagnostics.HealthChecks.HealthStatus? failureStatus, System.Collections.Generic.IEnumerable tags, System.TimeSpan? timeout) { } public System.Func Factory { get { throw null; } set { } } public Microsoft.Extensions.Diagnostics.HealthChecks.HealthStatus FailureStatus { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } public string Name { get { throw null; } set { } } public System.Collections.Generic.ISet Tags { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } } + public System.TimeSpan Timeout { get { throw null; } set { } } } [System.Runtime.InteropServices.StructLayoutAttribute(System.Runtime.InteropServices.LayoutKind.Sequential)] public partial struct HealthCheckResult diff --git a/src/HealthChecks/Abstractions/src/HealthCheckRegistration.cs b/src/HealthChecks/Abstractions/src/HealthCheckRegistration.cs index 9291c38846..8ee11e3195 100644 --- a/src/HealthChecks/Abstractions/src/HealthCheckRegistration.cs +++ b/src/HealthChecks/Abstractions/src/HealthCheckRegistration.cs @@ -1,4 +1,4 @@ -// Copyright (c) .NET Foundation. All rights reserved. +// 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; @@ -24,6 +24,22 @@ namespace Microsoft.Extensions.Diagnostics.HealthChecks { private Func _factory; private string _name; + private TimeSpan _timeout; + + /// + /// Creates a new for an existing instance. + /// + /// The health check name. + /// The instance. + /// + /// The that should be reported upon failure of the health check. If the provided value + /// is null, then will be reported. + /// + /// A list of tags that can be used for filtering health checks. + public HealthCheckRegistration(string name, IHealthCheck instance, HealthStatus? failureStatus, IEnumerable tags) + : this(name, instance, failureStatus, tags, default) + { + } /// /// Creates a new for an existing instance. @@ -35,7 +51,8 @@ namespace Microsoft.Extensions.Diagnostics.HealthChecks /// is null, then will be reported. /// /// A list of tags that can be used for filtering health checks. - public HealthCheckRegistration(string name, IHealthCheck instance, HealthStatus? failureStatus, IEnumerable tags) + /// An optional representing the timeout of the check. + public HealthCheckRegistration(string name, IHealthCheck instance, HealthStatus? failureStatus, IEnumerable tags, TimeSpan? timeout) { if (name == null) { @@ -47,10 +64,16 @@ namespace Microsoft.Extensions.Diagnostics.HealthChecks throw new ArgumentNullException(nameof(instance)); } + if (timeout <= TimeSpan.Zero && timeout != System.Threading.Timeout.InfiniteTimeSpan) + { + throw new ArgumentOutOfRangeException(nameof(timeout)); + } + Name = name; FailureStatus = failureStatus ?? HealthStatus.Unhealthy; Tags = new HashSet(tags ?? Array.Empty(), StringComparer.OrdinalIgnoreCase); Factory = (_) => instance; + Timeout = timeout ?? System.Threading.Timeout.InfiniteTimeSpan; } /// @@ -68,6 +91,27 @@ namespace Microsoft.Extensions.Diagnostics.HealthChecks Func factory, HealthStatus? failureStatus, IEnumerable tags) + : this(name, factory, failureStatus, tags, default) + { + } + + /// + /// Creates a new for an existing instance. + /// + /// The health check name. + /// A delegate used to create the instance. + /// + /// The that should be reported when the health check reports a failure. If the provided value + /// is null, then will be reported. + /// + /// A list of tags that can be used for filtering health checks. + /// An optional representing the timeout of the check. + public HealthCheckRegistration( + string name, + Func factory, + HealthStatus? failureStatus, + IEnumerable tags, + TimeSpan? timeout) { if (name == null) { @@ -79,10 +123,16 @@ namespace Microsoft.Extensions.Diagnostics.HealthChecks throw new ArgumentNullException(nameof(factory)); } + if (timeout <= TimeSpan.Zero && timeout != System.Threading.Timeout.InfiniteTimeSpan) + { + throw new ArgumentOutOfRangeException(nameof(timeout)); + } + Name = name; FailureStatus = failureStatus ?? HealthStatus.Unhealthy; Tags = new HashSet(tags ?? Array.Empty(), StringComparer.OrdinalIgnoreCase); Factory = factory; + Timeout = timeout ?? System.Threading.Timeout.InfiniteTimeSpan; } /// @@ -107,6 +157,23 @@ namespace Microsoft.Extensions.Diagnostics.HealthChecks /// public HealthStatus FailureStatus { get; set; } + /// + /// Gets or sets the timeout used for the test. + /// + public TimeSpan Timeout + { + get => _timeout; + set + { + if (value <= TimeSpan.Zero && value != System.Threading.Timeout.InfiniteTimeSpan) + { + throw new ArgumentOutOfRangeException(nameof(value)); + } + + _timeout = value; + } + } + /// /// Gets or sets the health check name. /// diff --git a/src/HealthChecks/HealthChecks/ref/Microsoft.Extensions.Diagnostics.HealthChecks.netstandard2.0.cs b/src/HealthChecks/HealthChecks/ref/Microsoft.Extensions.Diagnostics.HealthChecks.netstandard2.0.cs index 58bdc80602..a23961efdd 100644 --- a/src/HealthChecks/HealthChecks/ref/Microsoft.Extensions.Diagnostics.HealthChecks.netstandard2.0.cs +++ b/src/HealthChecks/HealthChecks/ref/Microsoft.Extensions.Diagnostics.HealthChecks.netstandard2.0.cs @@ -5,18 +5,25 @@ namespace Microsoft.Extensions.DependencyInjection { public static partial class HealthChecksBuilderAddCheckExtensions { - public static Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder AddCheck(this Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder builder, string name, Microsoft.Extensions.Diagnostics.HealthChecks.IHealthCheck instance, Microsoft.Extensions.Diagnostics.HealthChecks.HealthStatus? failureStatus = default(Microsoft.Extensions.Diagnostics.HealthChecks.HealthStatus?), System.Collections.Generic.IEnumerable tags = null) { throw null; } - public static Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder AddCheck(this Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder builder, string name, Microsoft.Extensions.Diagnostics.HealthChecks.HealthStatus? failureStatus = default(Microsoft.Extensions.Diagnostics.HealthChecks.HealthStatus?), System.Collections.Generic.IEnumerable tags = null) where T : class, Microsoft.Extensions.Diagnostics.HealthChecks.IHealthCheck { throw null; } + public static Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder AddCheck(this Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder builder, string name, Microsoft.Extensions.Diagnostics.HealthChecks.IHealthCheck instance, Microsoft.Extensions.Diagnostics.HealthChecks.HealthStatus? failureStatus, System.Collections.Generic.IEnumerable tags) { throw null; } + public static Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder AddCheck(this Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder builder, string name, Microsoft.Extensions.Diagnostics.HealthChecks.IHealthCheck instance, Microsoft.Extensions.Diagnostics.HealthChecks.HealthStatus? failureStatus = default(Microsoft.Extensions.Diagnostics.HealthChecks.HealthStatus?), System.Collections.Generic.IEnumerable tags = null, System.TimeSpan? timeout = default(System.TimeSpan?)) { throw null; } + public static Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder AddCheck(this Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder builder, string name, Microsoft.Extensions.Diagnostics.HealthChecks.HealthStatus? failureStatus, System.Collections.Generic.IEnumerable tags) where T : class, Microsoft.Extensions.Diagnostics.HealthChecks.IHealthCheck { throw null; } + public static Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder AddCheck(this Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder builder, string name, Microsoft.Extensions.Diagnostics.HealthChecks.HealthStatus? failureStatus = default(Microsoft.Extensions.Diagnostics.HealthChecks.HealthStatus?), System.Collections.Generic.IEnumerable tags = null, System.TimeSpan? timeout = default(System.TimeSpan?)) where T : class, Microsoft.Extensions.Diagnostics.HealthChecks.IHealthCheck { throw null; } public static Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder AddTypeActivatedCheck(this Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder builder, string name, Microsoft.Extensions.Diagnostics.HealthChecks.HealthStatus? failureStatus, System.Collections.Generic.IEnumerable tags, params object[] args) where T : class, Microsoft.Extensions.Diagnostics.HealthChecks.IHealthCheck { throw null; } + public static Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder AddTypeActivatedCheck(this Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder builder, string name, Microsoft.Extensions.Diagnostics.HealthChecks.HealthStatus? failureStatus, System.Collections.Generic.IEnumerable tags, System.TimeSpan timeout, params object[] args) where T : class, Microsoft.Extensions.Diagnostics.HealthChecks.IHealthCheck { throw null; } public static Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder AddTypeActivatedCheck(this Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder builder, string name, Microsoft.Extensions.Diagnostics.HealthChecks.HealthStatus? failureStatus, params object[] args) where T : class, Microsoft.Extensions.Diagnostics.HealthChecks.IHealthCheck { throw null; } public static Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder AddTypeActivatedCheck(this Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder builder, string name, params object[] args) where T : class, Microsoft.Extensions.Diagnostics.HealthChecks.IHealthCheck { throw null; } } public static partial class HealthChecksBuilderDelegateExtensions { - public static Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder AddAsyncCheck(this Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder builder, string name, System.Func> check, System.Collections.Generic.IEnumerable tags = null) { throw null; } - public static Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder AddAsyncCheck(this Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder builder, string name, System.Func> check, System.Collections.Generic.IEnumerable tags = null) { throw null; } - public static Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder AddCheck(this Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder builder, string name, System.Func check, System.Collections.Generic.IEnumerable tags = null) { throw null; } - public static Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder AddCheck(this Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder builder, string name, System.Func check, System.Collections.Generic.IEnumerable tags = null) { throw null; } + public static Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder AddAsyncCheck(this Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder builder, string name, System.Func> check, System.Collections.Generic.IEnumerable tags) { throw null; } + public static Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder AddAsyncCheck(this Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder builder, string name, System.Func> check, System.Collections.Generic.IEnumerable tags = null, System.TimeSpan? timeout = default(System.TimeSpan?)) { throw null; } + public static Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder AddAsyncCheck(this Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder builder, string name, System.Func> check, System.Collections.Generic.IEnumerable tags) { throw null; } + public static Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder AddAsyncCheck(this Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder builder, string name, System.Func> check, System.Collections.Generic.IEnumerable tags = null, System.TimeSpan? timeout = default(System.TimeSpan?)) { throw null; } + public static Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder AddCheck(this Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder builder, string name, System.Func check, System.Collections.Generic.IEnumerable tags) { throw null; } + public static Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder AddCheck(this Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder builder, string name, System.Func check, System.Collections.Generic.IEnumerable tags = null, System.TimeSpan? timeout = default(System.TimeSpan?)) { throw null; } + public static Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder AddCheck(this Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder builder, string name, System.Func check, System.Collections.Generic.IEnumerable tags) { throw null; } + public static Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder AddCheck(this Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder builder, string name, System.Func check, System.Collections.Generic.IEnumerable tags = null, System.TimeSpan? timeout = default(System.TimeSpan?)) { throw null; } } public static partial class HealthCheckServiceCollectionExtensions { diff --git a/src/HealthChecks/HealthChecks/src/DefaultHealthCheckService.cs b/src/HealthChecks/HealthChecks/src/DefaultHealthCheckService.cs index da8c00a5aa..56ce966d18 100644 --- a/src/HealthChecks/HealthChecks/src/DefaultHealthCheckService.cs +++ b/src/HealthChecks/HealthChecks/src/DefaultHealthCheckService.cs @@ -91,9 +91,21 @@ namespace Microsoft.Extensions.Diagnostics.HealthChecks Log.HealthCheckBegin(_logger, registration); HealthReportEntry entry; + CancellationTokenSource timeoutCancellationTokenSource = null; try { - var result = await healthCheck.CheckHealthAsync(context, cancellationToken); + HealthCheckResult result; + + var checkCancellationToken = cancellationToken; + if (registration.Timeout > TimeSpan.Zero) + { + timeoutCancellationTokenSource = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken); + timeoutCancellationTokenSource.CancelAfter(registration.Timeout); + checkCancellationToken = timeoutCancellationTokenSource.Token; + } + + result = await healthCheck.CheckHealthAsync(context, checkCancellationToken); + var duration = stopwatch.GetElapsedTime(); entry = new HealthReportEntry( @@ -107,7 +119,20 @@ namespace Microsoft.Extensions.Diagnostics.HealthChecks Log.HealthCheckData(_logger, registration, entry); } - // Allow cancellation to propagate. + catch (OperationCanceledException ex) when (!cancellationToken.IsCancellationRequested) + { + var duration = stopwatch.GetElapsedTime(); + entry = new HealthReportEntry( + status: HealthStatus.Unhealthy, + description: "A timeout occured while running check.", + duration: duration, + exception: ex, + data: null); + + Log.HealthCheckError(_logger, registration, ex, duration); + } + + // Allow cancellation to propagate if it's not a timeout. catch (Exception ex) when (ex as OperationCanceledException == null) { var duration = stopwatch.GetElapsedTime(); @@ -121,6 +146,11 @@ namespace Microsoft.Extensions.Diagnostics.HealthChecks Log.HealthCheckError(_logger, registration, ex, duration); } + finally + { + timeoutCancellationTokenSource?.Dispose(); + } + return entry; } } diff --git a/src/HealthChecks/HealthChecks/src/DependencyInjection/HealthChecksBuilderAddCheckExtensions.cs b/src/HealthChecks/HealthChecks/src/DependencyInjection/HealthChecksBuilderAddCheckExtensions.cs index 9508889054..74ee30290c 100644 --- a/src/HealthChecks/HealthChecks/src/DependencyInjection/HealthChecksBuilderAddCheckExtensions.cs +++ b/src/HealthChecks/HealthChecks/src/DependencyInjection/HealthChecksBuilderAddCheckExtensions.cs @@ -24,12 +24,37 @@ namespace Microsoft.Extensions.DependencyInjection /// /// A list of tags that can be used to filter health checks. /// The . + // 2.0 BACKCOMPAT OVERLOAD -- DO NOT TOUCH + public static IHealthChecksBuilder AddCheck( + this IHealthChecksBuilder builder, + string name, + IHealthCheck instance, + HealthStatus? failureStatus, + IEnumerable tags) + { + return AddCheck(builder, name, instance, failureStatus, tags, default); + } + + /// + /// Adds a new health check with the specified name and implementation. + /// + /// The . + /// The name of the health check. + /// An instance. + /// + /// The that should be reported when the health check reports a failure. If the provided value + /// is null, then will be reported. + /// + /// A list of tags that can be used to filter health checks. + /// An optional representing the timeout of the check. + /// The . public static IHealthChecksBuilder AddCheck( this IHealthChecksBuilder builder, string name, IHealthCheck instance, HealthStatus? failureStatus = null, - IEnumerable tags = null) + IEnumerable tags = null, + TimeSpan? timeout = null) { if (builder == null) { @@ -46,7 +71,7 @@ namespace Microsoft.Extensions.DependencyInjection throw new ArgumentNullException(nameof(instance)); } - return builder.Add(new HealthCheckRegistration(name, instance, failureStatus, tags)); + return builder.Add(new HealthCheckRegistration(name, instance, failureStatus, tags, timeout)); } /// @@ -63,15 +88,45 @@ namespace Microsoft.Extensions.DependencyInjection /// The . /// /// This method will use to create the health check - /// instance when needed. If a service of type is registred in the dependency injection container - /// with any liftime it will be used. Otherwise an instance of type will be constructed with + /// instance when needed. If a service of type is registered in the dependency injection container + /// with any lifetime it will be used. Otherwise an instance of type will be constructed with + /// access to services from the dependency injection container. + /// + // 2.0 BACKCOMPAT OVERLOAD -- DO NOT TOUCH + public static IHealthChecksBuilder AddCheck( + this IHealthChecksBuilder builder, + string name, + HealthStatus? failureStatus, + IEnumerable tags) where T : class, IHealthCheck + { + return AddCheck(builder, name, failureStatus, tags, default); + } + + /// + /// Adds a new health check with the specified name and implementation. + /// + /// The health check implementation type. + /// The . + /// The name of the health check. + /// + /// The that should be reported when the health check reports a failure. If the provided value + /// is null, then will be reported. + /// + /// A list of tags that can be used to filter health checks. + /// An optional representing the timeout of the check. + /// The . + /// + /// This method will use to create the health check + /// instance when needed. If a service of type is registered in the dependency injection container + /// with any lifetime it will be used. Otherwise an instance of type will be constructed with /// access to services from the dependency injection container. /// public static IHealthChecksBuilder AddCheck( this IHealthChecksBuilder builder, string name, HealthStatus? failureStatus = null, - IEnumerable tags = null) where T : class, IHealthCheck + IEnumerable tags = null, + TimeSpan? timeout = null) where T : class, IHealthCheck { if (builder == null) { @@ -83,7 +138,7 @@ namespace Microsoft.Extensions.DependencyInjection throw new ArgumentNullException(nameof(name)); } - return builder.Add(new HealthCheckRegistration(name, s => ActivatorUtilities.GetServiceOrCreateInstance(s), failureStatus, tags)); + return builder.Add(new HealthCheckRegistration(name, s => ActivatorUtilities.GetServiceOrCreateInstance(s), failureStatus, tags, timeout)); } // NOTE: AddTypeActivatedCheck has overloads rather than default parameters values, because default parameter values don't @@ -187,5 +242,44 @@ namespace Microsoft.Extensions.DependencyInjection return builder.Add(new HealthCheckRegistration(name, s => ActivatorUtilities.CreateInstance(s, args), failureStatus, tags)); } + + /// + /// Adds a new type activated health check with the specified name and implementation. + /// + /// The health check implementation type. + /// The . + /// The name of the health check. + /// + /// The that should be reported when the health check reports a failure. If the provided value + /// is null, then will be reported. + /// + /// A list of tags that can be used to filter health checks. + /// Additional arguments to provide to the constructor. + /// A representing the timeout of the check. + /// The . + /// + /// This method will use to create the health check + /// instance when needed. Additional arguments can be provided to the constructor via . + /// + public static IHealthChecksBuilder AddTypeActivatedCheck( + this IHealthChecksBuilder builder, + string name, + HealthStatus? failureStatus, + IEnumerable tags, + TimeSpan timeout, + params object[] args) where T : class, IHealthCheck + { + if (builder == null) + { + throw new ArgumentNullException(nameof(builder)); + } + + if (name == null) + { + throw new ArgumentNullException(nameof(name)); + } + + return builder.Add(new HealthCheckRegistration(name, s => ActivatorUtilities.CreateInstance(s, args), failureStatus, tags, timeout)); + } } } diff --git a/src/HealthChecks/HealthChecks/src/DependencyInjection/HealthChecksBuilderDelegateExtensions.cs b/src/HealthChecks/HealthChecks/src/DependencyInjection/HealthChecksBuilderDelegateExtensions.cs index d7dfdd90ae..ba27ab5554 100644 --- a/src/HealthChecks/HealthChecks/src/DependencyInjection/HealthChecksBuilderDelegateExtensions.cs +++ b/src/HealthChecks/HealthChecks/src/DependencyInjection/HealthChecksBuilderDelegateExtensions.cs @@ -1,4 +1,4 @@ -// Copyright (c) .NET Foundation. All rights reserved. +// 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; @@ -22,11 +22,31 @@ namespace Microsoft.Extensions.DependencyInjection /// A list of tags that can be used to filter health checks. /// A delegate that provides the health check implementation. /// The . + // 2.0 BACKCOMPAT OVERLOAD -- DO NOT TOUCH public static IHealthChecksBuilder AddCheck( this IHealthChecksBuilder builder, string name, Func check, - IEnumerable tags = null) + IEnumerable tags) + { + return AddCheck(builder, name, check, tags, default); + } + + /// + /// Adds a new health check with the specified name and implementation. + /// + /// The . + /// The name of the health check. + /// A list of tags that can be used to filter health checks. + /// A delegate that provides the health check implementation. + /// An optional representing the timeout of the check. + /// The . + public static IHealthChecksBuilder AddCheck( + this IHealthChecksBuilder builder, + string name, + Func check, + IEnumerable tags = null, + TimeSpan? timeout = default) { if (builder == null) { @@ -44,7 +64,7 @@ namespace Microsoft.Extensions.DependencyInjection } var instance = new DelegateHealthCheck((ct) => Task.FromResult(check())); - return builder.Add(new HealthCheckRegistration(name, instance, failureStatus: null, tags)); + return builder.Add(new HealthCheckRegistration(name, instance, failureStatus: null, tags, timeout)); } /// @@ -55,11 +75,31 @@ namespace Microsoft.Extensions.DependencyInjection /// A list of tags that can be used to filter health checks. /// A delegate that provides the health check implementation. /// The . + // 2.0 BACKCOMPAT OVERLOAD -- DO NOT TOUCH public static IHealthChecksBuilder AddCheck( this IHealthChecksBuilder builder, string name, Func check, - IEnumerable tags = null) + IEnumerable tags) + { + return AddCheck(builder, name, check, tags, default); + } + + /// + /// Adds a new health check with the specified name and implementation. + /// + /// The . + /// The name of the health check. + /// A list of tags that can be used to filter health checks. + /// A delegate that provides the health check implementation. + /// An optional representing the timeout of the check. + /// The . + public static IHealthChecksBuilder AddCheck( + this IHealthChecksBuilder builder, + string name, + Func check, + IEnumerable tags = null, + TimeSpan? timeout = default) { if (builder == null) { @@ -77,7 +117,7 @@ namespace Microsoft.Extensions.DependencyInjection } var instance = new DelegateHealthCheck((ct) => Task.FromResult(check(ct))); - return builder.Add(new HealthCheckRegistration(name, instance, failureStatus: null, tags)); + return builder.Add(new HealthCheckRegistration(name, instance, failureStatus: null, tags, timeout)); } /// @@ -88,11 +128,31 @@ namespace Microsoft.Extensions.DependencyInjection /// A list of tags that can be used to filter health checks. /// A delegate that provides the health check implementation. /// The . + // 2.0 BACKCOMPAT OVERLOAD -- DO NOT TOUCH public static IHealthChecksBuilder AddAsyncCheck( this IHealthChecksBuilder builder, string name, Func> check, - IEnumerable tags = null) + IEnumerable tags) + { + return AddAsyncCheck(builder, name, check, tags, default); + } + + /// + /// Adds a new health check with the specified name and implementation. + /// + /// The . + /// The name of the health check. + /// A list of tags that can be used to filter health checks. + /// A delegate that provides the health check implementation. + /// An optional representing the timeout of the check. + /// The . + public static IHealthChecksBuilder AddAsyncCheck( + this IHealthChecksBuilder builder, + string name, + Func> check, + IEnumerable tags = null, + TimeSpan? timeout = default) { if (builder == null) { @@ -110,7 +170,7 @@ namespace Microsoft.Extensions.DependencyInjection } var instance = new DelegateHealthCheck((ct) => check()); - return builder.Add(new HealthCheckRegistration(name, instance, failureStatus: null, tags)); + return builder.Add(new HealthCheckRegistration(name, instance, failureStatus: null, tags, timeout)); } /// @@ -121,11 +181,31 @@ namespace Microsoft.Extensions.DependencyInjection /// A list of tags that can be used to filter health checks. /// A delegate that provides the health check implementation. /// The . + // 2.0 BACKCOMPAT OVERLOAD -- DO NOT TOUCH public static IHealthChecksBuilder AddAsyncCheck( this IHealthChecksBuilder builder, string name, Func> check, - IEnumerable tags = null) + IEnumerable tags) + { + return AddAsyncCheck(builder, name, check, tags, default); + } + + /// + /// Adds a new health check with the specified name and implementation. + /// + /// The . + /// The name of the health check. + /// A list of tags that can be used to filter health checks. + /// A delegate that provides the health check implementation. + /// An optional representing the timeout of the check. + /// The . + public static IHealthChecksBuilder AddAsyncCheck( + this IHealthChecksBuilder builder, + string name, + Func> check, + IEnumerable tags = null, + TimeSpan? timeout = default) { if (builder == null) { @@ -143,7 +223,7 @@ namespace Microsoft.Extensions.DependencyInjection } var instance = new DelegateHealthCheck((ct) => check(ct)); - return builder.Add(new HealthCheckRegistration(name, instance, failureStatus: null, tags)); + return builder.Add(new HealthCheckRegistration(name, instance, failureStatus: null, tags, timeout)); } } } diff --git a/src/HealthChecks/HealthChecks/test/DefaultHealthCheckServiceTest.cs b/src/HealthChecks/HealthChecks/test/DefaultHealthCheckServiceTest.cs index fa08cccd20..ba0a2f32d5 100644 --- a/src/HealthChecks/HealthChecks/test/DefaultHealthCheckServiceTest.cs +++ b/src/HealthChecks/HealthChecks/test/DefaultHealthCheckServiceTest.cs @@ -424,6 +424,32 @@ namespace Microsoft.Extensions.Diagnostics.HealthChecks }); } + [Fact] + public async Task CheckHealthAsync_TimeoutReturnsUnhealthy() + { + // Arrange + var service = CreateHealthChecksService(b => + { + b.AddAsyncCheck("timeout", async (ct) => + { + await Task.Delay(2000, ct); + return HealthCheckResult.Healthy(); + }, timeout: TimeSpan.FromMilliseconds(100)); + }); + + // Act + var results = await service.CheckHealthAsync(); + + // Assert + Assert.Collection( + results.Entries, + actual => + { + Assert.Equal("timeout", actual.Key); + Assert.Equal(HealthStatus.Unhealthy, actual.Value.Status); + }); + } + private static DefaultHealthCheckService CreateHealthChecksService(Action configure) { var services = new ServiceCollection(); From a623a5c20fa46d4ea5e3b460af8f9934fd93fbd3 Mon Sep 17 00:00:00 2001 From: Ryan Nowak Date: Fri, 22 Feb 2019 17:49:48 -0800 Subject: [PATCH 0071/1101] Fix dotnet/extensions#598 and dotnet/extensions#684 Pass args into type activator \n\nCommit migrated from https://github.com/dotnet/extensions/commit/a787ee2870d3cfc416334f37195f0e6f52570474 --- .../HealthChecksBuilderAddCheckExtensions.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/HealthChecks/HealthChecks/src/DependencyInjection/HealthChecksBuilderAddCheckExtensions.cs b/src/HealthChecks/HealthChecks/src/DependencyInjection/HealthChecksBuilderAddCheckExtensions.cs index 74ee30290c..51b7815438 100644 --- a/src/HealthChecks/HealthChecks/src/DependencyInjection/HealthChecksBuilderAddCheckExtensions.cs +++ b/src/HealthChecks/HealthChecks/src/DependencyInjection/HealthChecksBuilderAddCheckExtensions.cs @@ -168,7 +168,7 @@ namespace Microsoft.Extensions.DependencyInjection throw new ArgumentNullException(nameof(name)); } - return AddTypeActivatedCheck(builder, name, failureStatus: null, tags: null); + return AddTypeActivatedCheck(builder, name, failureStatus: null, tags: null, args); } /// @@ -203,7 +203,7 @@ namespace Microsoft.Extensions.DependencyInjection throw new ArgumentNullException(nameof(name)); } - return AddTypeActivatedCheck(builder, name, failureStatus, tags: null); + return AddTypeActivatedCheck(builder, name, failureStatus, tags: null, args); } /// From 09b9a49da6d0e9b06a4fc3a05ae11ecb43149e33 Mon Sep 17 00:00:00 2001 From: Martin Costello Date: Mon, 4 Mar 2019 18:14:16 +0000 Subject: [PATCH 0072/1101] Atomically swap config data (dotnet/extensions#1202) Swap the configuration providers' data atomically, rather than directly changing the property, so that any enumeration of the dictionary running during the reload operation does not throw an InvalidOperationException due to the collection being modified. Relates to dotnet/extensions#1189.\n\nCommit migrated from https://github.com/dotnet/extensions/commit/192abfdf3e73106e40d7651eecfb621e4f78c344 --- .../src/KeyPerFileConfigurationProvider.cs | 7 ++- .../test/KeyPerFileTests.cs | 43 +++++++++++++++++++ 2 files changed, 48 insertions(+), 2 deletions(-) diff --git a/src/Configuration.KeyPerFile/src/KeyPerFileConfigurationProvider.cs b/src/Configuration.KeyPerFile/src/KeyPerFileConfigurationProvider.cs index 13541110e6..2e33b9dfcd 100644 --- a/src/Configuration.KeyPerFile/src/KeyPerFileConfigurationProvider.cs +++ b/src/Configuration.KeyPerFile/src/KeyPerFileConfigurationProvider.cs @@ -31,12 +31,13 @@ namespace Microsoft.Extensions.Configuration.KeyPerFile /// public override void Load() { - Data = new Dictionary(StringComparer.OrdinalIgnoreCase); + var data = new Dictionary(StringComparer.OrdinalIgnoreCase); if (Source.FileProvider == null) { if (Source.Optional) { + Data = data; return; } @@ -61,10 +62,12 @@ namespace Microsoft.Extensions.Configuration.KeyPerFile { if (Source.IgnoreCondition == null || !Source.IgnoreCondition(file.Name)) { - Data.Add(NormalizeKey(file.Name), TrimNewLine(streamReader.ReadToEnd())); + data.Add(NormalizeKey(file.Name), TrimNewLine(streamReader.ReadToEnd())); } } } + + Data = data; } private string GetDirectoryName() diff --git a/src/Configuration.KeyPerFile/test/KeyPerFileTests.cs b/src/Configuration.KeyPerFile/test/KeyPerFileTests.cs index 4de528c011..838e62222d 100644 --- a/src/Configuration.KeyPerFile/test/KeyPerFileTests.cs +++ b/src/Configuration.KeyPerFile/test/KeyPerFileTests.cs @@ -6,6 +6,8 @@ using System.Collections; using System.Collections.Generic; using System.IO; using System.Text; +using System.Threading; +using System.Threading.Tasks; using Microsoft.Extensions.FileProviders; using Microsoft.Extensions.Primitives; using Xunit; @@ -179,6 +181,47 @@ namespace Microsoft.Extensions.Configuration.KeyPerFile.Test Assert.Equal("SecretValue1", config["ignore.Secret1"]); Assert.Equal("SecretValue2", config["Secret2"]); } + + [Fact] + public void BindingDoesNotThrowIfReloadedDuringBinding() + { + var testFileProvider = new TestFileProvider( + new TestFile("Number", "-2"), + new TestFile("Text", "Foo")); + + var config = new ConfigurationBuilder() + .AddKeyPerFile(o => o.FileProvider = testFileProvider) + .Build(); + + MyOptions options = null; + + using (var cts = new CancellationTokenSource(TimeSpan.FromMilliseconds(250))) + { + void ReloadLoop() + { + while (!cts.IsCancellationRequested) + { + config.Reload(); + } + } + + _ = Task.Run(ReloadLoop); + + while (!cts.IsCancellationRequested) + { + options = config.Get(); + } + } + + Assert.Equal(-2, options.Number); + Assert.Equal("Foo", options.Text); + } + + private sealed class MyOptions + { + public int Number { get; set; } + public string Text { get; set; } + } } class TestFileProvider : IFileProvider From b13ea4cd548e560a5cfe32fedb9fef9a7a32f7bd Mon Sep 17 00:00:00 2001 From: Martin Costello Date: Tue, 5 Mar 2019 17:43:59 +0000 Subject: [PATCH 0073/1101] Use localhost for HttpListener (dotnet/extensions#1206) Use localhost instead of 127.0.0.1 to fix dotnet/extensions#1205.\n\nCommit migrated from https://github.com/dotnet/extensions/commit/99b4ddbdbc86bffdafa76d7cac568b6b5ca46f1c --- src/Testing/test/HttpClientSlimTest.cs | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/Testing/test/HttpClientSlimTest.cs b/src/Testing/test/HttpClientSlimTest.cs index 42b19ece08..ede48243e5 100644 --- a/src/Testing/test/HttpClientSlimTest.cs +++ b/src/Testing/test/HttpClientSlimTest.cs @@ -4,7 +4,6 @@ using System; using System.Net; using System.Net.Http; -using System.Net.Sockets; using System.Text; using System.Threading.Tasks; using Xunit; @@ -13,7 +12,7 @@ namespace Microsoft.AspNetCore.Testing { public class HttpClientSlimTest { - private static byte[] _defaultResponse = Encoding.ASCII.GetBytes("test"); + private static readonly byte[] _defaultResponse = Encoding.ASCII.GetBytes("test"); [Fact] public async Task GetStringAsyncHttp() @@ -79,7 +78,7 @@ namespace Microsoft.AspNetCore.Testing // HttpListener doesn't support requesting port 0 (dynamic). // Requesting port 0 from Sockets and then passing that to HttpListener is racy. // Just keep trying until we find a free one. - address = $"http://127.0.0.1:{random.Next(1024, ushort.MaxValue)}/"; + address = $"http://localhost:{random.Next(1024, ushort.MaxValue)}/"; listener.Prefixes.Add(address); listener.Start(); break; From d9627c80efda3434cf4608e1af70bbff142fb50e Mon Sep 17 00:00:00 2001 From: Andrew Stanton-Nurse Date: Wed, 6 Mar 2019 15:19:11 -0800 Subject: [PATCH 0074/1101] add FlakyAttribute to mark flaky tests (dotnet/extensions#1222) part of aspnet/AspNetCoredotnet/extensions#8237\n\nCommit migrated from https://github.com/dotnet/extensions/commit/42e9a7d712d1b513c32961dca7ad6d0bd33d0fae --- src/Testing/src/AzurePipelines.cs | 17 ++++ src/Testing/src/HelixQueues.cs | 26 ++++++ src/Testing/src/xunit/FlakyAttribute.cs | 75 +++++++++++++++ src/Testing/src/xunit/FlakyTestDiscoverer.cs | 38 ++++++++ src/Testing/test/FlakyAttributeTest.cs | 97 ++++++++++++++++++++ 5 files changed, 253 insertions(+) create mode 100644 src/Testing/src/AzurePipelines.cs create mode 100644 src/Testing/src/HelixQueues.cs create mode 100644 src/Testing/src/xunit/FlakyAttribute.cs create mode 100644 src/Testing/src/xunit/FlakyTestDiscoverer.cs create mode 100644 src/Testing/test/FlakyAttributeTest.cs diff --git a/src/Testing/src/AzurePipelines.cs b/src/Testing/src/AzurePipelines.cs new file mode 100644 index 0000000000..ae1eac3b90 --- /dev/null +++ b/src/Testing/src/AzurePipelines.cs @@ -0,0 +1,17 @@ +using System; +using System.Collections.Generic; +using System.Reflection; + +namespace Microsoft.AspNetCore.Testing +{ + public static class AzurePipelines + { + public const string All = Prefix + "All"; + public const string Windows = OsPrefix + "Windows_NT"; + public const string macOS = OsPrefix + "Darwin"; + public const string Linux = OsPrefix + "Linux"; + + private const string Prefix = "AzP:"; + private const string OsPrefix = Prefix + "OS:"; + } +} diff --git a/src/Testing/src/HelixQueues.cs b/src/Testing/src/HelixQueues.cs new file mode 100644 index 0000000000..84828b6b83 --- /dev/null +++ b/src/Testing/src/HelixQueues.cs @@ -0,0 +1,26 @@ +using System; +using System.Collections.Generic; +using System.Reflection; + +namespace Microsoft.AspNetCore.Testing +{ + public static class HelixQueues + { + public const string All = Prefix + "All"; + + public const string Fedora28Amd64 = QueuePrefix + "Fedora.28." + Amd64Suffix; + public const string Fedora27Amd64 = QueuePrefix + "Fedora.27." + Amd64Suffix; + public const string Redhat7Amd64 = QueuePrefix + "Redhat.7." + Amd64Suffix; + public const string Debian9Amd64 = QueuePrefix + "Debian.9." + Amd64Suffix; + public const string Debian8Amd64 = QueuePrefix + "Debian.8." + Amd64Suffix; + public const string Centos7Amd64 = QueuePrefix + "Centos.7." + Amd64Suffix; + public const string Ubuntu1604Amd64 = QueuePrefix + "Ubuntu.1604." + Amd64Suffix; + public const string Ubuntu1810Amd64 = QueuePrefix + "Ubuntu.1810." + Amd64Suffix; + public const string macOS1012Amd64 = QueuePrefix + "OSX.1012." + Amd64Suffix; + public const string Windows10Amd64 = QueuePrefix + "Windows.10.Amd64.ClientRS4.VS2017.Open"; // Doesn't have the default suffix! + + private const string Prefix = "Helix:"; + private const string QueuePrefix = Prefix + "Queue:"; + private const string Amd64Suffix = "Amd64.Open"; + } +} diff --git a/src/Testing/src/xunit/FlakyAttribute.cs b/src/Testing/src/xunit/FlakyAttribute.cs new file mode 100644 index 0000000000..b613a9bf4d --- /dev/null +++ b/src/Testing/src/xunit/FlakyAttribute.cs @@ -0,0 +1,75 @@ +using System; +using System.Collections.Generic; +using Xunit.Sdk; + +namespace Microsoft.AspNetCore.Testing.xunit +{ + /// + /// Marks a test as "Flaky" so that the build will sequester it and ignore failures. + /// + /// + /// + /// This attribute works by applying xUnit.net "Traits" based on the criteria specified in the attribute + /// properties. Once these traits are applied, build scripts can include/exclude tests based on them. + /// + /// + /// All flakiness-related traits start with Flaky: and are grouped first by the process running the tests: Azure Pipelines (AzP) or Helix. + /// Then there is a segment specifying the "selector" which indicates where the test is flaky. Finally a segment specifying the value of that selector. + /// The value of these traits is always either "true" or the trait is not present. We encode the entire selector in the name of the trait because xUnit.net only + /// provides "==" and "!=" operators for traits, there is no way to check if a trait "contains" or "does not contain" a value. VSTest does support "contains" checks + /// but does not appear to support "does not contain" checks. Using this pattern means we can use simple "==" and "!=" checks to either only run flaky tests, or exclude + /// flaky tests. + /// + /// + /// + /// + /// [Fact] + /// [Flaky("...", HelixQueues.Fedora28Amd64, AzurePipelines.macOS)] + /// public void FlakyTest() + /// { + /// // Flakiness + /// } + /// + /// + /// + /// The above example generates the following facets: + /// + /// + /// + /// + /// Flaky:Helix:Queue:Fedora.28.Amd64.Open = true + /// + /// + /// Flaky:AzP:OS:Darwin = true + /// + /// + /// + /// + /// Given the above attribute, the Azure Pipelines macOS run can easily filter this test out by passing -notrait "Flaky:AzP:OS:all=true" -notrait "Flaky:AzP:OS:Darwin=true" + /// to xunit.console.exe. Similarly, it can run only flaky tests using -trait "Flaky:AzP:OS:all=true" -trait "Flaky:AzP:OS:Darwin=true" + /// + /// + [TraitDiscoverer("Microsoft.AspNetCore.Testing.xunit.FlakyTestDiscoverer", "Microsoft.AspNetCore.Testing")] + [AttributeUsage(AttributeTargets.Method)] + public sealed class FlakyAttribute : Attribute, ITraitAttribute + { + /// + /// Gets a URL to a GitHub issue tracking this flaky test. + /// + public string GitHubIssueUrl { get; } + + public IReadOnlyList Filters { get; } + + /// + /// Initializes a new instance of the class with the specified and a list of . If no + /// filters are provided, the test is considered flaky in all environments. + /// + /// The URL to a GitHub issue tracking this flaky test. + /// A list of filters that define where this test is flaky. Use values in and . + public FlakyAttribute(string gitHubIssueUrl, params string[] filters) + { + GitHubIssueUrl = gitHubIssueUrl; + Filters = new List(filters); + } + } +} diff --git a/src/Testing/src/xunit/FlakyTestDiscoverer.cs b/src/Testing/src/xunit/FlakyTestDiscoverer.cs new file mode 100644 index 0000000000..344b9b2378 --- /dev/null +++ b/src/Testing/src/xunit/FlakyTestDiscoverer.cs @@ -0,0 +1,38 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using Xunit.Abstractions; +using Xunit.Sdk; + +namespace Microsoft.AspNetCore.Testing.xunit +{ + public class FlakyTestDiscoverer : ITraitDiscoverer + { + public IEnumerable> GetTraits(IAttributeInfo traitAttribute) + { + if (traitAttribute is ReflectionAttributeInfo attribute && attribute.Attribute is FlakyAttribute flakyAttribute) + { + return GetTraitsCore(flakyAttribute); + } + else + { + throw new InvalidOperationException("The 'Flaky' attribute is only supported via reflection."); + } + } + + private IEnumerable> GetTraitsCore(FlakyAttribute attribute) + { + if (attribute.Filters.Count > 0) + { + foreach (var filter in attribute.Filters) + { + yield return new KeyValuePair($"Flaky:{filter}", "true"); + } + } + else + { + yield return new KeyValuePair($"Flaky:All", "true"); + } + } + } +} diff --git a/src/Testing/test/FlakyAttributeTest.cs b/src/Testing/test/FlakyAttributeTest.cs new file mode 100644 index 0000000000..e9accf6274 --- /dev/null +++ b/src/Testing/test/FlakyAttributeTest.cs @@ -0,0 +1,97 @@ +using Microsoft.AspNetCore.Testing.xunit; +using System; +using System.Collections.Generic; +using Xunit; + +namespace Microsoft.AspNetCore.Testing.Tests +{ + public class FlakyAttributeTest + { + [Fact] + [Flaky("http://example.com")] + public void AlwaysFlaky() + { + if (!string.IsNullOrEmpty(Environment.GetEnvironmentVariable("HELIX")) || !string.IsNullOrEmpty(Environment.GetEnvironmentVariable("AGENT_OS"))) + { + throw new Exception("Flaky!"); + } + } + + [Fact] + [Flaky("http://example.com", HelixQueues.All)] + public void FlakyInHelixOnly() + { + if (!string.IsNullOrEmpty(Environment.GetEnvironmentVariable("HELIX"))) + { + throw new Exception("Flaky on Helix!"); + } + } + + [Fact] + [Flaky("http://example.com", HelixQueues.macOS1012Amd64, HelixQueues.Fedora28Amd64)] + public void FlakyInSpecificHelixQueue() + { + // Today we don't run Extensions tests on Helix, but this test should light up when we do. + var queueName = Environment.GetEnvironmentVariable("HELIX"); + if (!string.IsNullOrEmpty(queueName)) + { + var failingQueues = new HashSet(StringComparer.OrdinalIgnoreCase) { HelixQueues.macOS1012Amd64, HelixQueues.Fedora28Amd64 }; + if (failingQueues.Contains(queueName)) + { + throw new Exception($"Flaky on Helix Queue '{queueName}' !"); + } + } + } + + [Fact] + [Flaky("http://example.com", AzurePipelines.All)] + public void FlakyInAzPOnly() + { + if (!string.IsNullOrEmpty(Environment.GetEnvironmentVariable("AGENT_OS"))) + { + throw new Exception("Flaky on AzP!"); + } + } + + [Fact] + [Flaky("http://example.com", AzurePipelines.Windows)] + public void FlakyInAzPWindowsOnly() + { + if (string.Equals(Environment.GetEnvironmentVariable("AGENT_OS"), AzurePipelines.Windows)) + { + throw new Exception("Flaky on AzP Windows!"); + } + } + + [Fact] + [Flaky("http://example.com", AzurePipelines.macOS)] + public void FlakyInAzPmacOSOnly() + { + if (string.Equals(Environment.GetEnvironmentVariable("AGENT_OS"), AzurePipelines.macOS)) + { + throw new Exception("Flaky on AzP macOS!"); + } + } + + [Fact] + [Flaky("http://example.com", AzurePipelines.Linux)] + public void FlakyInAzPLinuxOnly() + { + if (string.Equals(Environment.GetEnvironmentVariable("AGENT_OS"), AzurePipelines.Linux)) + { + throw new Exception("Flaky on AzP Linux!"); + } + } + + [Fact] + [Flaky("http://example.com", AzurePipelines.Linux, AzurePipelines.macOS)] + public void FlakyInAzPNonWindowsOnly() + { + var agentOs = Environment.GetEnvironmentVariable("AGENT_OS"); + if (string.Equals(agentOs, "Linux") || string.Equals(agentOs, AzurePipelines.macOS)) + { + throw new Exception("Flaky on AzP non-Windows!"); + } + } + } +} From 262262569a3d6f204330ce38b801ede9b7bf77d0 Mon Sep 17 00:00:00 2001 From: Andrew Stanton-Nurse Date: Wed, 6 Mar 2019 15:19:11 -0800 Subject: [PATCH 0075/1101] add FlakyAttribute to mark flaky tests (#1222) part of aspnet/AspNetCore#8237 --- src/Testing/src/LoggedTest/LoggedTestBase.cs | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/Testing/src/LoggedTest/LoggedTestBase.cs b/src/Testing/src/LoggedTest/LoggedTestBase.cs index 72de6a87c3..492de61cb6 100644 --- a/src/Testing/src/LoggedTest/LoggedTestBase.cs +++ b/src/Testing/src/LoggedTest/LoggedTestBase.cs @@ -91,6 +91,14 @@ namespace Microsoft.Extensions.Logging.Testing public virtual void Dispose() { + if(_testLog == null) + { + // It seems like sometimes the MSBuild goop that adds the test framework can end up in a bad state and not actually add it + // Not sure yet why that happens but the exception isn't clear so I'm adding this error so we can detect it better. + // -anurse + throw new InvalidOperationException("LoggedTest base class was used but nothing initialized it! The test framework may not be enabled. Try cleaning your 'obj' directory."); + } + _initializationException?.Throw(); _testLog.Dispose(); } From d5a386b0a98a97235346c456dabff6cd54bcb241 Mon Sep 17 00:00:00 2001 From: Hao Kung Date: Thu, 7 Mar 2019 12:25:22 -0800 Subject: [PATCH 0076/1101] Remove newtonsoft ref and sharedsource false flag (dotnet/extensions#1219) \n\nCommit migrated from https://github.com/dotnet/extensions/commit/d563ecb057c7b5687be1a2f8aeb949280f0df75f --- src/Configuration.KeyPerFile/Directory.Build.props | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Configuration.KeyPerFile/Directory.Build.props b/src/Configuration.KeyPerFile/Directory.Build.props index fe35a9faec..63d0c8b102 100644 --- a/src/Configuration.KeyPerFile/Directory.Build.props +++ b/src/Configuration.KeyPerFile/Directory.Build.props @@ -4,5 +4,6 @@ true configuration + $(NoWarn);PKG0001 From 3e9a592422f4fe9a64545b2b0ecc7884b725e7fa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?K=C3=A9vin=20Chalet?= Date: Fri, 8 Mar 2019 00:42:29 +0100 Subject: [PATCH 0077/1101] Make the generic IStringLocalizer interface covariant (dotnet/extensions#1117) Make the generic IStringLocalizer interface covariant \n\nCommit migrated from https://github.com/dotnet/extensions/commit/90020a9608688a259c7e653b4096270568337290 --- ...ocalization.Abstractions.netstandard2.0.cs | 2 +- .../Abstractions/src/IStringLocalizerOfT.cs | 4 +- .../test/StringLocalizerOfTTest.cs | 159 ++++++++++++++++++ 3 files changed, 162 insertions(+), 3 deletions(-) create mode 100644 src/Localization/Localization/test/StringLocalizerOfTTest.cs diff --git a/src/Localization/Abstractions/ref/Microsoft.Extensions.Localization.Abstractions.netstandard2.0.cs b/src/Localization/Abstractions/ref/Microsoft.Extensions.Localization.Abstractions.netstandard2.0.cs index 3f9f422021..174cac28e5 100644 --- a/src/Localization/Abstractions/ref/Microsoft.Extensions.Localization.Abstractions.netstandard2.0.cs +++ b/src/Localization/Abstractions/ref/Microsoft.Extensions.Localization.Abstractions.netstandard2.0.cs @@ -16,7 +16,7 @@ namespace Microsoft.Extensions.Localization Microsoft.Extensions.Localization.IStringLocalizer Create(string baseName, string location); Microsoft.Extensions.Localization.IStringLocalizer Create(System.Type resourceSource); } - public partial interface IStringLocalizer : Microsoft.Extensions.Localization.IStringLocalizer + public partial interface IStringLocalizer : Microsoft.Extensions.Localization.IStringLocalizer { } public partial class LocalizedString diff --git a/src/Localization/Abstractions/src/IStringLocalizerOfT.cs b/src/Localization/Abstractions/src/IStringLocalizerOfT.cs index 695678a900..bdc2a1c7b7 100644 --- a/src/Localization/Abstractions/src/IStringLocalizerOfT.cs +++ b/src/Localization/Abstractions/src/IStringLocalizerOfT.cs @@ -7,8 +7,8 @@ namespace Microsoft.Extensions.Localization /// Represents an that provides strings for . /// /// The to provide strings for. - public interface IStringLocalizer : IStringLocalizer + public interface IStringLocalizer : IStringLocalizer { } -} \ No newline at end of file +} diff --git a/src/Localization/Localization/test/StringLocalizerOfTTest.cs b/src/Localization/Localization/test/StringLocalizerOfTTest.cs new file mode 100644 index 0000000000..ce06e74d1c --- /dev/null +++ b/src/Localization/Localization/test/StringLocalizerOfTTest.cs @@ -0,0 +1,159 @@ +// 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.Globalization; +using Moq; +using Xunit; + +namespace Microsoft.Extensions.Localization +{ + public class StringLocalizerOfTTest + { + [Fact] + public void Constructor_ThrowsAnExceptionForNullFactory() + { + // Arrange, act and assert + var exception = Assert.Throws( + () => new StringLocalizer(factory: null)); + + Assert.Equal("factory", exception.ParamName); + } + + [Fact] + public void Constructor_ResolvesLocalizerFromFactory() + { + // Arrange + var factory = new Mock(); + + // Act + _ = new StringLocalizer(factory.Object); + + // Assert + factory.Verify(mock => mock.Create(typeof(object)), Times.Once()); + } + + [Fact] + public void WithCulture_InvokesWithCultureFromInnerLocalizer() + { + // Arrange + var factory = new Mock(); + var innerLocalizer = new Mock(); + factory.Setup(mock => mock.Create(typeof(object))) + .Returns(innerLocalizer.Object); + + var localizer = new StringLocalizer(factory.Object); + + // Act +#pragma warning disable CS0618 + localizer.WithCulture(CultureInfo.GetCultureInfo("fr-FR")); +#pragma warning restore CS0618 + + // Assert +#pragma warning disable CS0618 + innerLocalizer.Verify(mock => mock.WithCulture(CultureInfo.GetCultureInfo("fr-FR")), Times.Once()); +#pragma warning restore CS0618 + } + + [Fact] + public void Indexer_ThrowsAnExceptionForNullName() + { + // Arrange + var factory = new Mock(); + var innerLocalizer = new Mock(); + factory.Setup(mock => mock.Create(typeof(object))) + .Returns(innerLocalizer.Object); + + var localizer = new StringLocalizer(factory.Object); + + // Act and assert + var exception = Assert.Throws(() => localizer[name: null]); + + Assert.Equal("name", exception.ParamName); + } + + [Fact] + public void Indexer_InvokesIndexerFromInnerLocalizer() + { + // Arrange + var factory = new Mock(); + var innerLocalizer = new Mock(); + factory.Setup(mock => mock.Create(typeof(object))) + .Returns(innerLocalizer.Object); + + var localizer = new StringLocalizer(factory.Object); + + // Act + _ = localizer["Hello world"]; + + // Assert + innerLocalizer.Verify(mock => mock["Hello world"], Times.Once()); + } + + [Fact] + public void Indexer_ThrowsAnExceptionForNullName_WithArguments() + { + // Arrange + var factory = new Mock(); + var innerLocalizer = new Mock(); + factory.Setup(mock => mock.Create(typeof(object))) + .Returns(innerLocalizer.Object); + + var localizer = new StringLocalizer(factory.Object); + + // Act and assert + var exception = Assert.Throws(() => localizer[name: null]); + + Assert.Equal("name", exception.ParamName); + } + + [Fact] + public void Indexer_InvokesIndexerFromInnerLocalizer_WithArguments() + { + // Arrange + var factory = new Mock(); + var innerLocalizer = new Mock(); + factory.Setup(mock => mock.Create(typeof(object))) + .Returns(innerLocalizer.Object); + + var localizer = new StringLocalizer(factory.Object); + + // Act + _ = localizer["Welcome, {0}", "Bob"]; + + // Assert + innerLocalizer.Verify(mock => mock["Welcome, {0}", "Bob"], Times.Once()); + } + + [Fact] + public void GetAllStrings_InvokesGetAllStringsFromInnerLocalizer() + { + // Arrange + var factory = new Mock(); + var innerLocalizer = new Mock(); + factory.Setup(mock => mock.Create(typeof(object))) + .Returns(innerLocalizer.Object); + + var localizer = new StringLocalizer(factory.Object); + + // Act + localizer.GetAllStrings(includeParentCultures: true); + + // Assert + innerLocalizer.Verify(mock => mock.GetAllStrings(true), Times.Once()); + } + + [Fact] + public void StringLocalizer_CanBeCastToBaseType() + { + // Arrange and act + IStringLocalizer localizer = new StringLocalizer(Mock.Of()); + + // Assert + Assert.NotNull(localizer); + } + + private class BaseType { } + private class DerivedType : BaseType { } + } +} From c9ff2af852cfd8f874a8e8bcf9d2971915405942 Mon Sep 17 00:00:00 2001 From: Javier Calvarro Nelson Date: Mon, 11 Mar 2019 18:00:14 +0100 Subject: [PATCH 0078/1101] Skip test until we move to the new .NET Core implementation (dotnet/extensions#1156) \n\nCommit migrated from https://github.com/dotnet/extensions/commit/4636215c9b813fef51be55bf4853848bed221a6e --- .../Microsoft.JSInterop/test/JSInProcessRuntimeBaseTest.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/JSInterop/Microsoft.JSInterop/test/JSInProcessRuntimeBaseTest.cs b/src/JSInterop/Microsoft.JSInterop/test/JSInProcessRuntimeBaseTest.cs index 36474fe407..a13d53677a 100644 --- a/src/JSInterop/Microsoft.JSInterop/test/JSInProcessRuntimeBaseTest.cs +++ b/src/JSInterop/Microsoft.JSInterop/test/JSInProcessRuntimeBaseTest.cs @@ -10,7 +10,7 @@ namespace Microsoft.JSInterop.Tests { public class JSInProcessRuntimeBaseTest { - [Fact] + [Fact(Skip = "https://github.com/aspnet/AspNetCore-Internal/issues/1807#issuecomment-470756811")] public void DispatchesSyncCallsAndDeserializesResults() { // Arrange From 1bfa807e48ee04f8837c1e0c3467dc785271fc1b Mon Sep 17 00:00:00 2001 From: Hao Kung Date: Thu, 14 Mar 2019 21:14:45 -0700 Subject: [PATCH 0079/1101] Port config fix to 2.2 (dotnet/extensions#1221) - port of dotnet/extensions#1202 - with PR tweaks for 2.2 - e.g. adjust Microsoft.Extensions.Configuration.FunctionalTests.csproj to match layout here - update PatchConfig.props and NuGetPackageVerifier.json\n\nCommit migrated from https://github.com/dotnet/extensions/commit/9ebff1a64e5ea460da1e9837fab3e96939e0ad0e --- .../src/KeyPerFileConfigurationProvider.cs | 7 ++-- .../test/KeyPerFileTests.cs | 35 +++++++++++++++++++ ...ions.Configuration.KeyPerFile.Tests.csproj | 1 + 3 files changed, 41 insertions(+), 2 deletions(-) diff --git a/src/Configuration.KeyPerFile/src/KeyPerFileConfigurationProvider.cs b/src/Configuration.KeyPerFile/src/KeyPerFileConfigurationProvider.cs index 4748895744..6e4234ecf3 100644 --- a/src/Configuration.KeyPerFile/src/KeyPerFileConfigurationProvider.cs +++ b/src/Configuration.KeyPerFile/src/KeyPerFileConfigurationProvider.cs @@ -31,12 +31,13 @@ namespace Microsoft.Extensions.Configuration.KeyPerFile /// public override void Load() { - Data = new Dictionary(StringComparer.OrdinalIgnoreCase); + var data = new Dictionary(StringComparer.OrdinalIgnoreCase); if (Source.FileProvider == null) { if (Source.Optional) { + Data = data; return; } else @@ -63,10 +64,12 @@ namespace Microsoft.Extensions.Configuration.KeyPerFile { if (Source.IgnoreCondition == null || !Source.IgnoreCondition(file.Name)) { - Data.Add(NormalizeKey(file.Name), TrimNewLine(streamReader.ReadToEnd())); + data.Add(NormalizeKey(file.Name), TrimNewLine(streamReader.ReadToEnd())); } } } + + Data = data; } } } diff --git a/src/Configuration.KeyPerFile/test/KeyPerFileTests.cs b/src/Configuration.KeyPerFile/test/KeyPerFileTests.cs index d409c0eab0..499c25106c 100644 --- a/src/Configuration.KeyPerFile/test/KeyPerFileTests.cs +++ b/src/Configuration.KeyPerFile/test/KeyPerFileTests.cs @@ -4,6 +4,8 @@ using System.Collections.Generic; using System.IO; using System.Linq; using System.Text; +using System.Threading; +using System.Threading.Tasks; using Microsoft.Extensions.FileProviders; using Microsoft.Extensions.Primitives; using Xunit; @@ -177,6 +179,39 @@ namespace Microsoft.Extensions.Configuration.KeyPerFile.Test Assert.Equal("SecretValue1", config["ignore.Secret1"]); Assert.Equal("SecretValue2", config["Secret2"]); } + + [Fact] + public void BindingDoesNotThrowIfReloadedDuringBinding() + { + var testFileProvider = new TestFileProvider( + new TestFile("Number", "-2"), + new TestFile("Text", "Foo")); + + var config = new ConfigurationBuilder() + .AddKeyPerFile(o => o.FileProvider = testFileProvider) + .Build(); + + MyOptions options = null; + + using (var cts = new CancellationTokenSource(TimeSpan.FromMilliseconds(250))) + { + _ = Task.Run(() => { while (!cts.IsCancellationRequested) config.Reload(); }); + + while (!cts.IsCancellationRequested) + { + options = config.Get(); + } + } + + Assert.Equal(-2, options.Number); + Assert.Equal("Foo", options.Text); + } + + private sealed class MyOptions + { + public int Number { get; set; } + public string Text { get; set; } + } } class TestFileProvider : IFileProvider diff --git a/src/Configuration.KeyPerFile/test/Microsoft.Extensions.Configuration.KeyPerFile.Tests.csproj b/src/Configuration.KeyPerFile/test/Microsoft.Extensions.Configuration.KeyPerFile.Tests.csproj index 4205f4ae13..154fd5bb62 100644 --- a/src/Configuration.KeyPerFile/test/Microsoft.Extensions.Configuration.KeyPerFile.Tests.csproj +++ b/src/Configuration.KeyPerFile/test/Microsoft.Extensions.Configuration.KeyPerFile.Tests.csproj @@ -5,6 +5,7 @@ + From 2d1743a2f672201f3886ea084fde069ca74d3f86 Mon Sep 17 00:00:00 2001 From: Andrew Stanton-Nurse Date: Tue, 19 Mar 2019 18:43:25 -0700 Subject: [PATCH 0080/1101] clean up flaky filter constants (dotnet/extensions#1248) \n\nCommit migrated from https://github.com/dotnet/extensions/commit/3b3a23eb1a2dc7dbd57ab192da9856ed3ad8e8ea --- src/Testing/src/AzurePipelines.cs | 17 ---------- src/Testing/src/FlakyOn.cs | 41 +++++++++++++++++++++++++ src/Testing/src/HelixQueues.cs | 28 ++++++----------- src/Testing/src/xunit/FlakyAttribute.cs | 23 ++++++++++++-- src/Testing/test/FlakyAttributeTest.cs | 26 ++++++++-------- 5 files changed, 84 insertions(+), 51 deletions(-) delete mode 100644 src/Testing/src/AzurePipelines.cs create mode 100644 src/Testing/src/FlakyOn.cs diff --git a/src/Testing/src/AzurePipelines.cs b/src/Testing/src/AzurePipelines.cs deleted file mode 100644 index ae1eac3b90..0000000000 --- a/src/Testing/src/AzurePipelines.cs +++ /dev/null @@ -1,17 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Reflection; - -namespace Microsoft.AspNetCore.Testing -{ - public static class AzurePipelines - { - public const string All = Prefix + "All"; - public const string Windows = OsPrefix + "Windows_NT"; - public const string macOS = OsPrefix + "Darwin"; - public const string Linux = OsPrefix + "Linux"; - - private const string Prefix = "AzP:"; - private const string OsPrefix = Prefix + "OS:"; - } -} diff --git a/src/Testing/src/FlakyOn.cs b/src/Testing/src/FlakyOn.cs new file mode 100644 index 0000000000..81d9299043 --- /dev/null +++ b/src/Testing/src/FlakyOn.cs @@ -0,0 +1,41 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace Microsoft.AspNetCore.Testing +{ + public static class FlakyOn + { + public const string All = "All"; + + public static class Helix + { + public const string All = QueuePrefix + "All"; + + public const string Fedora28Amd64 = QueuePrefix + HelixQueues.Fedora28Amd64; + public const string Fedora27Amd64 = QueuePrefix + HelixQueues.Fedora27Amd64; + public const string Redhat7Amd64 = QueuePrefix + HelixQueues.Redhat7Amd64; + public const string Debian9Amd64 = QueuePrefix + HelixQueues.Debian9Amd64; + public const string Debian8Amd64 = QueuePrefix + HelixQueues.Debian8Amd64; + public const string Centos7Amd64 = QueuePrefix + HelixQueues.Centos7Amd64; + public const string Ubuntu1604Amd64 = QueuePrefix + HelixQueues.Ubuntu1604Amd64; + public const string Ubuntu1810Amd64 = QueuePrefix + HelixQueues.Ubuntu1810Amd64; + public const string macOS1012Amd64 = QueuePrefix + HelixQueues.macOS1012Amd64; + public const string Windows10Amd64 = QueuePrefix + HelixQueues.Windows10Amd64; + + private const string Prefix = "Helix:"; + private const string QueuePrefix = Prefix + "Queue:"; + } + + public static class AzP + { + public const string All = Prefix + "All"; + public const string Windows = OsPrefix + "Windows_NT"; + public const string macOS = OsPrefix + "Darwin"; + public const string Linux = OsPrefix + "Linux"; + + private const string Prefix = "AzP:"; + private const string OsPrefix = Prefix + "OS:"; + } + } +} diff --git a/src/Testing/src/HelixQueues.cs b/src/Testing/src/HelixQueues.cs index 84828b6b83..ef5e4d1f5a 100644 --- a/src/Testing/src/HelixQueues.cs +++ b/src/Testing/src/HelixQueues.cs @@ -1,26 +1,18 @@ -using System; -using System.Collections.Generic; -using System.Reflection; - namespace Microsoft.AspNetCore.Testing { public static class HelixQueues { - public const string All = Prefix + "All"; + public const string Fedora28Amd64 = "Fedora.28." + Amd64Suffix; + public const string Fedora27Amd64 = "Fedora.27." + Amd64Suffix; + public const string Redhat7Amd64 = "Redhat.7." + Amd64Suffix; + public const string Debian9Amd64 = "Debian.9." + Amd64Suffix; + public const string Debian8Amd64 = "Debian.8." + Amd64Suffix; + public const string Centos7Amd64 = "Centos.7." + Amd64Suffix; + public const string Ubuntu1604Amd64 = "Ubuntu.1604." + Amd64Suffix; + public const string Ubuntu1810Amd64 = "Ubuntu.1810." + Amd64Suffix; + public const string macOS1012Amd64 = "OSX.1012." + Amd64Suffix; + public const string Windows10Amd64 = "Windows.10.Amd64.ClientRS4.VS2017.Open"; // Doesn't have the default suffix! - public const string Fedora28Amd64 = QueuePrefix + "Fedora.28." + Amd64Suffix; - public const string Fedora27Amd64 = QueuePrefix + "Fedora.27." + Amd64Suffix; - public const string Redhat7Amd64 = QueuePrefix + "Redhat.7." + Amd64Suffix; - public const string Debian9Amd64 = QueuePrefix + "Debian.9." + Amd64Suffix; - public const string Debian8Amd64 = QueuePrefix + "Debian.8." + Amd64Suffix; - public const string Centos7Amd64 = QueuePrefix + "Centos.7." + Amd64Suffix; - public const string Ubuntu1604Amd64 = QueuePrefix + "Ubuntu.1604." + Amd64Suffix; - public const string Ubuntu1810Amd64 = QueuePrefix + "Ubuntu.1810." + Amd64Suffix; - public const string macOS1012Amd64 = QueuePrefix + "OSX.1012." + Amd64Suffix; - public const string Windows10Amd64 = QueuePrefix + "Windows.10.Amd64.ClientRS4.VS2017.Open"; // Doesn't have the default suffix! - - private const string Prefix = "Helix:"; - private const string QueuePrefix = Prefix + "Queue:"; private const string Amd64Suffix = "Amd64.Open"; } } diff --git a/src/Testing/src/xunit/FlakyAttribute.cs b/src/Testing/src/xunit/FlakyAttribute.cs index b613a9bf4d..f58026c7ca 100644 --- a/src/Testing/src/xunit/FlakyAttribute.cs +++ b/src/Testing/src/xunit/FlakyAttribute.cs @@ -64,12 +64,29 @@ namespace Microsoft.AspNetCore.Testing.xunit /// Initializes a new instance of the class with the specified and a list of . If no /// filters are provided, the test is considered flaky in all environments. /// + /// + /// At least one filter is required. + /// /// The URL to a GitHub issue tracking this flaky test. - /// A list of filters that define where this test is flaky. Use values in and . - public FlakyAttribute(string gitHubIssueUrl, params string[] filters) + /// The first filter that indicates where the test is flaky. Use a value from . + /// A list of additional filters that define where this test is flaky. Use values in . + public FlakyAttribute(string gitHubIssueUrl, string firstFilter, params string[] additionalFilters) { + if(string.IsNullOrEmpty(gitHubIssueUrl)) + { + throw new ArgumentNullException(nameof(gitHubIssueUrl)); + } + + if(string.IsNullOrEmpty(firstFilter)) + { + throw new ArgumentNullException(nameof(firstFilter)); + } + GitHubIssueUrl = gitHubIssueUrl; - Filters = new List(filters); + var filters = new List(); + filters.Add(firstFilter); + filters.AddRange(additionalFilters); + Filters = filters; } } } diff --git a/src/Testing/test/FlakyAttributeTest.cs b/src/Testing/test/FlakyAttributeTest.cs index e9accf6274..7837bd8711 100644 --- a/src/Testing/test/FlakyAttributeTest.cs +++ b/src/Testing/test/FlakyAttributeTest.cs @@ -8,8 +8,8 @@ namespace Microsoft.AspNetCore.Testing.Tests public class FlakyAttributeTest { [Fact] - [Flaky("http://example.com")] - public void AlwaysFlaky() + [Flaky("http://example.com", FlakyOn.All)] + public void AlwaysFlakyInCI() { if (!string.IsNullOrEmpty(Environment.GetEnvironmentVariable("HELIX")) || !string.IsNullOrEmpty(Environment.GetEnvironmentVariable("AGENT_OS"))) { @@ -18,7 +18,7 @@ namespace Microsoft.AspNetCore.Testing.Tests } [Fact] - [Flaky("http://example.com", HelixQueues.All)] + [Flaky("http://example.com", FlakyOn.Helix.All)] public void FlakyInHelixOnly() { if (!string.IsNullOrEmpty(Environment.GetEnvironmentVariable("HELIX"))) @@ -28,7 +28,7 @@ namespace Microsoft.AspNetCore.Testing.Tests } [Fact] - [Flaky("http://example.com", HelixQueues.macOS1012Amd64, HelixQueues.Fedora28Amd64)] + [Flaky("http://example.com", FlakyOn.Helix.macOS1012Amd64, FlakyOn.Helix.Fedora28Amd64)] public void FlakyInSpecificHelixQueue() { // Today we don't run Extensions tests on Helix, but this test should light up when we do. @@ -44,7 +44,7 @@ namespace Microsoft.AspNetCore.Testing.Tests } [Fact] - [Flaky("http://example.com", AzurePipelines.All)] + [Flaky("http://example.com", FlakyOn.AzP.All)] public void FlakyInAzPOnly() { if (!string.IsNullOrEmpty(Environment.GetEnvironmentVariable("AGENT_OS"))) @@ -54,41 +54,41 @@ namespace Microsoft.AspNetCore.Testing.Tests } [Fact] - [Flaky("http://example.com", AzurePipelines.Windows)] + [Flaky("http://example.com", FlakyOn.AzP.Windows)] public void FlakyInAzPWindowsOnly() { - if (string.Equals(Environment.GetEnvironmentVariable("AGENT_OS"), AzurePipelines.Windows)) + if (string.Equals(Environment.GetEnvironmentVariable("AGENT_OS"), "Windows_NT")) { throw new Exception("Flaky on AzP Windows!"); } } [Fact] - [Flaky("http://example.com", AzurePipelines.macOS)] + [Flaky("http://example.com", FlakyOn.AzP.macOS)] public void FlakyInAzPmacOSOnly() { - if (string.Equals(Environment.GetEnvironmentVariable("AGENT_OS"), AzurePipelines.macOS)) + if (string.Equals(Environment.GetEnvironmentVariable("AGENT_OS"), "Darwin")) { throw new Exception("Flaky on AzP macOS!"); } } [Fact] - [Flaky("http://example.com", AzurePipelines.Linux)] + [Flaky("http://example.com", FlakyOn.AzP.Linux)] public void FlakyInAzPLinuxOnly() { - if (string.Equals(Environment.GetEnvironmentVariable("AGENT_OS"), AzurePipelines.Linux)) + if (string.Equals(Environment.GetEnvironmentVariable("AGENT_OS"), "Linux")) { throw new Exception("Flaky on AzP Linux!"); } } [Fact] - [Flaky("http://example.com", AzurePipelines.Linux, AzurePipelines.macOS)] + [Flaky("http://example.com", FlakyOn.AzP.Linux, FlakyOn.AzP.macOS)] public void FlakyInAzPNonWindowsOnly() { var agentOs = Environment.GetEnvironmentVariable("AGENT_OS"); - if (string.Equals(agentOs, "Linux") || string.Equals(agentOs, AzurePipelines.macOS)) + if (string.Equals(agentOs, "Linux") || string.Equals(agentOs, "Darwin")) { throw new Exception("Flaky on AzP non-Windows!"); } From ffc4b007645683a85dbb57146ce9caf6e9ce6fdc Mon Sep 17 00:00:00 2001 From: Ryan Nowak Date: Sun, 17 Feb 2019 16:16:56 -0800 Subject: [PATCH 0081/1101] Generalize and harden ST-sync-context \n\nCommit migrated from https://github.com/dotnet/extensions/commit/247bb34a63a0ef097396cb939189b70e3a12321a --- .../SingleThreadedSynchronizationContext.cs | 45 +++++++++++++++++++ 1 file changed, 45 insertions(+) create mode 100644 src/Shared/test/Shared.Tests/SingleThreadedSynchronizationContext.cs diff --git a/src/Shared/test/Shared.Tests/SingleThreadedSynchronizationContext.cs b/src/Shared/test/Shared.Tests/SingleThreadedSynchronizationContext.cs new file mode 100644 index 0000000000..77312e0a05 --- /dev/null +++ b/src/Shared/test/Shared.Tests/SingleThreadedSynchronizationContext.cs @@ -0,0 +1,45 @@ +// 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.Collections.Concurrent; +using System.Threading; + +namespace Microsoft.Extensions.Internal +{ + internal class SingleThreadedSynchronizationContext : SynchronizationContext + { + private readonly BlockingCollection<(SendOrPostCallback Callback, object State)> _queue = new BlockingCollection<(SendOrPostCallback Callback, object State)>(); + + public override void Send(SendOrPostCallback d, object state) // Sync operations + { + throw new NotSupportedException($"{nameof(SingleThreadedSynchronizationContext)} does not support synchronous operations."); + } + + public override void Post(SendOrPostCallback d, object state) // Async operations + { + _queue.Add((d, state)); + } + + public static void Run(Action action) + { + var previous = Current; + var context = new SingleThreadedSynchronizationContext(); + SetSynchronizationContext(context); + try + { + action(); + + while (context._queue.TryTake(out var item)) + { + item.Callback(item.State); + } + } + finally + { + context._queue.CompleteAdding(); + SetSynchronizationContext(previous); + } + } + } +} From 2849483895f57bc259ebc1ff16957feb1b3add45 Mon Sep 17 00:00:00 2001 From: Ryan Nowak Date: Sun, 17 Feb 2019 16:29:24 -0800 Subject: [PATCH 0082/1101] Make IHealthCheckService work in ST-sync-context \n\nCommit migrated from https://github.com/dotnet/extensions/commit/2b673c89c20193e2d2d0fd8aa200987a4f2505e7 --- .../src/DefaultHealthCheckService.cs | 7 ++-- .../test/DefaultHealthCheckServiceTest.cs | 34 +++++++++++++++++++ ...ions.Diagnostics.HealthChecks.Tests.csproj | 4 +++ 3 files changed, 40 insertions(+), 5 deletions(-) diff --git a/src/HealthChecks/HealthChecks/src/DefaultHealthCheckService.cs b/src/HealthChecks/HealthChecks/src/DefaultHealthCheckService.cs index 56ce966d18..c2c9084e0a 100644 --- a/src/HealthChecks/HealthChecks/src/DefaultHealthCheckService.cs +++ b/src/HealthChecks/HealthChecks/src/DefaultHealthCheckService.cs @@ -54,7 +54,7 @@ namespace Microsoft.Extensions.Diagnostics.HealthChecks { foreach (var registration in registrations) { - tasks[index++] = RunCheckAsync(scope, registration, cancellationToken); + tasks[index++] = Task.Run(() => RunCheckAsync(scope, registration, cancellationToken), cancellationToken); } await Task.WhenAll(tasks).ConfigureAwait(false); @@ -75,8 +75,6 @@ namespace Microsoft.Extensions.Diagnostics.HealthChecks private async Task RunCheckAsync(IServiceScope scope, HealthCheckRegistration registration, CancellationToken cancellationToken) { - await Task.Yield(); - cancellationToken.ThrowIfCancellationRequested(); var healthCheck = registration.Factory(scope.ServiceProvider); @@ -104,7 +102,7 @@ namespace Microsoft.Extensions.Diagnostics.HealthChecks checkCancellationToken = timeoutCancellationTokenSource.Token; } - result = await healthCheck.CheckHealthAsync(context, checkCancellationToken); + result = await healthCheck.CheckHealthAsync(context, checkCancellationToken).ConfigureAwait(false); var duration = stopwatch.GetElapsedTime(); @@ -118,7 +116,6 @@ namespace Microsoft.Extensions.Diagnostics.HealthChecks Log.HealthCheckEnd(_logger, registration, entry, duration); Log.HealthCheckData(_logger, registration, entry); } - catch (OperationCanceledException ex) when (!cancellationToken.IsCancellationRequested) { var duration = stopwatch.GetElapsedTime(); diff --git a/src/HealthChecks/HealthChecks/test/DefaultHealthCheckServiceTest.cs b/src/HealthChecks/HealthChecks/test/DefaultHealthCheckServiceTest.cs index ba0a2f32d5..38442edb93 100644 --- a/src/HealthChecks/HealthChecks/test/DefaultHealthCheckServiceTest.cs +++ b/src/HealthChecks/HealthChecks/test/DefaultHealthCheckServiceTest.cs @@ -8,6 +8,7 @@ using System.Threading; using System.Threading.Tasks; using Microsoft.AspNetCore.Testing; using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Internal; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging.Testing; using Microsoft.Extensions.Options; @@ -450,6 +451,39 @@ namespace Microsoft.Extensions.Diagnostics.HealthChecks }); } + [Fact] + public void CheckHealthAsync_WorksInSingleThreadedSyncContext() + { + // Arrange + var service = CreateHealthChecksService(b => + { + b.AddAsyncCheck("test", async () => + { + await Task.Delay(1).ConfigureAwait(false); + return HealthCheckResult.Healthy(); + }); + }); + + var hangs = true; + + // Act + using (var cts = new CancellationTokenSource(TimeSpan.FromSeconds(3))) + { + var token = cts.Token; + token.Register(() => throw new OperationCanceledException(token)); + + SingleThreadedSynchronizationContext.Run(() => + { + // Act + service.CheckHealthAsync(token).GetAwaiter().GetResult(); + hangs = false; + }); + } + + // Assert + Assert.False(hangs); + } + private static DefaultHealthCheckService CreateHealthChecksService(Action configure) { var services = new ServiceCollection(); diff --git a/src/HealthChecks/HealthChecks/test/Microsoft.Extensions.Diagnostics.HealthChecks.Tests.csproj b/src/HealthChecks/HealthChecks/test/Microsoft.Extensions.Diagnostics.HealthChecks.Tests.csproj index 163b618900..08cd6a35f1 100644 --- a/src/HealthChecks/HealthChecks/test/Microsoft.Extensions.Diagnostics.HealthChecks.Tests.csproj +++ b/src/HealthChecks/HealthChecks/test/Microsoft.Extensions.Diagnostics.HealthChecks.Tests.csproj @@ -12,4 +12,8 @@ + + + + From a8a3fbab33cbb863e1cb660a728df21c13adf0c5 Mon Sep 17 00:00:00 2001 From: "dotnet-maestro[bot]" Date: Wed, 27 Mar 2019 14:40:19 +0000 Subject: [PATCH 0083/1101] [master] Update dependencies from dotnet/arcade (dotnet/extensions#1279) * Update dependencies from https://github.com/dotnet/arcade build 20190321.6 - Microsoft.DotNet.Arcade.Sdk - 1.0.0-beta.19171.6 - Microsoft.DotNet.GenAPI - 1.0.0-beta.19171.6 - Microsoft.DotNet.Helix.Sdk - 2.0.0-beta.19171.6 * Use Arcade NuSpec helpers * Fix Release flaky test leg * Fix Release flaky non-Windows test legs * Update dependencies from https://github.com/dotnet/arcade build 20190325.9 - Microsoft.DotNet.Arcade.Sdk - 1.0.0-beta.19175.9 - Microsoft.DotNet.GenAPI - 1.0.0-beta.19175.9 - Microsoft.DotNet.Helix.Sdk - 2.0.0-beta.19175.9 * Update dependencies from https://github.com/dotnet/arcade build 20190326.14 - Microsoft.DotNet.Arcade.Sdk - 1.0.0-beta.19176.14 - Microsoft.DotNet.GenAPI - 1.0.0-beta.19176.14 - Microsoft.DotNet.Helix.Sdk - 2.0.0-beta.19176.14 \n\nCommit migrated from https://github.com/dotnet/extensions/commit/d353f7b0204136d352423d44ad6df0505d17a376 --- ...t.Extensions.FileProviders.Embedded.csproj | 39 +++++++------------ ...t.Extensions.FileProviders.Embedded.nuspec | 12 +----- 2 files changed, 16 insertions(+), 35 deletions(-) diff --git a/src/FileProviders/Embedded/src/Microsoft.Extensions.FileProviders.Embedded.csproj b/src/FileProviders/Embedded/src/Microsoft.Extensions.FileProviders.Embedded.csproj index 792b9ff5b3..7d84c19f9d 100644 --- a/src/FileProviders/Embedded/src/Microsoft.Extensions.FileProviders.Embedded.csproj +++ b/src/FileProviders/Embedded/src/Microsoft.Extensions.FileProviders.Embedded.csproj @@ -22,31 +22,22 @@ - + - - id=$(PackageId); - version=$(PackageVersion); - authors=$(Authors); - description=$(Description); - tags=$(PackageTags.Replace(';', ' ')); - licenseUrl=$(PackageLicenseUrl); - projectUrl=$(PackageProjectUrl); - iconUrl=$(PackageIconUrl); - repositoryUrl=$(RepositoryUrl); - repositoryCommit=$(RepositoryCommit); - copyright=$(Copyright); - targetframework=$(TargetFramework); - AssemblyName=$(AssemblyName); - - OutputBinary=@(BuiltProjectOutputGroupOutput); - OutputSymbol=@(DebugSymbolsProjectOutputGroupOutput); - OutputDocumentation=@(DocumentationProjectOutputGroupOutput); - - - TaskAssemblyNetStandard=$(ArtifactsDir)bin\$(AssemblyName).Manifest.Task\$(Configuration)\netstandard2.0\$(AssemblyName).Manifest.Task.dll; - TaskSymbolNetStandard=$(ArtifactsDir)bin\$(AssemblyName).Manifest.Task\$(Configuration)\netstandard2.0\$(AssemblyName).Manifest.Task.pdb; - + $(PackageTags.Replace(';',' ')) + <_OutputBinary>@(BuiltProjectOutputGroupOutput) + <_OutputSymbol>@(DebugSymbolsProjectOutputGroupOutput) + <_OutputDocumentation>@(DocumentationProjectOutputGroupOutput) + + + + + + + + + + diff --git a/src/FileProviders/Embedded/src/Microsoft.Extensions.FileProviders.Embedded.nuspec b/src/FileProviders/Embedded/src/Microsoft.Extensions.FileProviders.Embedded.nuspec index ff6d385add..4a33eb6a95 100644 --- a/src/FileProviders/Embedded/src/Microsoft.Extensions.FileProviders.Embedded.nuspec +++ b/src/FileProviders/Embedded/src/Microsoft.Extensions.FileProviders.Embedded.nuspec @@ -1,17 +1,7 @@ - $id$ - $version$ - $authors$ - true - $licenseUrl$ - $projectUrl$ - $iconUrl$ - $description$ - $copyright$ - $tags$ - + $CommonMetadataElements$ From f5c756c93533023c24dbcaa3c6d423c522cdb0e7 Mon Sep 17 00:00:00 2001 From: Ryan Nowak Date: Wed, 27 Mar 2019 19:42:23 -0700 Subject: [PATCH 0084/1101] Fix dotnet/extensions#8612 JSInterop throws truncated call stack The fix for this is to use more ExceptionDispatchInfo! Basically everwhere that we handle an exception is now an EDI. It's easy to pass these around and they do the right thing as far as perserving the stack in. I de-factored this code a little bit to make all of this work, but it's now pretty unambiguously correct upon inspection. I started out wanting to get rid of the continue-with because we've found that pretty error prone overall. However making this method async-void started to ask more questions than it answered. What happens if serialization of the exception fails? \n\nCommit migrated from https://github.com/dotnet/extensions/commit/0a27fd3ad6604ca1f1278db5e01224a29fcab00d --- .../src/DotNetDispatcher.cs | 69 ++++++++----------- .../Microsoft.JSInterop/src/JSRuntimeBase.cs | 5 ++ .../test/DotNetDispatcherTest.cs | 64 +++++++++++++++++ 3 files changed, 97 insertions(+), 41 deletions(-) diff --git a/src/JSInterop/Microsoft.JSInterop/src/DotNetDispatcher.cs b/src/JSInterop/Microsoft.JSInterop/src/DotNetDispatcher.cs index 0f346bbfdd..ac936e670a 100644 --- a/src/JSInterop/Microsoft.JSInterop/src/DotNetDispatcher.cs +++ b/src/JSInterop/Microsoft.JSInterop/src/DotNetDispatcher.cs @@ -7,6 +7,7 @@ using System.Collections.Concurrent; using System.Collections.Generic; using System.Linq; using System.Reflection; +using System.Runtime.ExceptionServices; using System.Threading.Tasks; namespace Microsoft.JSInterop @@ -72,8 +73,10 @@ namespace Microsoft.JSInterop ? null : jsRuntimeBaseInstance.ArgSerializerStrategy.FindDotNetObject(dotNetObjectId); + // Using ExceptionDispatchInfo here throughout because we want to always preserve + // original stack traces. object syncResult = null; - Exception syncException = null; + ExceptionDispatchInfo syncException = null; try { @@ -81,45 +84,38 @@ namespace Microsoft.JSInterop } catch (Exception ex) { - syncException = ex; + syncException = ExceptionDispatchInfo.Capture(ex); } // If there was no callId, the caller does not want to be notified about the result - if (callId != null) + if (callId == null) { - // Invoke and coerce the result to a Task so the caller can use the same async API - // for both synchronous and asynchronous methods - var task = CoerceToTask(syncResult, syncException); - - task.ContinueWith(completedTask => + return; + } + else if (syncException != null) + { + // Threw synchronously, let's respond. + jsRuntimeBaseInstance.EndInvokeDotNet(callId, false, syncException); + } + else if (syncResult is Task task) + { + // Returned a task - we need to continue that task and then report an exception + // or return the value. + task.ContinueWith(t => { - try + if (t.Exception != null) { - var result = TaskGenericsUtil.GetTaskResult(completedTask); - jsRuntimeBaseInstance.EndInvokeDotNet(callId, true, result); + var exception = t.Exception.GetBaseException(); + jsRuntimeBaseInstance.EndInvokeDotNet(callId, false, ExceptionDispatchInfo.Capture(exception)); } - catch (Exception ex) - { - ex = UnwrapException(ex); - jsRuntimeBaseInstance.EndInvokeDotNet(callId, false, ex); - } - }); - } - } - private static Task CoerceToTask(object syncResult, Exception syncException) - { - if (syncException != null) - { - return Task.FromException(syncException); - } - else if (syncResult is Task syncResultTask) - { - return syncResultTask; + var result = TaskGenericsUtil.GetTaskResult(task); + jsRuntimeBaseInstance.EndInvokeDotNet(callId, true, result); + }, TaskScheduler.Current); } else { - return Task.FromResult(syncResult); + jsRuntimeBaseInstance.EndInvokeDotNet(callId, true, syncResult); } } @@ -175,9 +171,10 @@ namespace Microsoft.JSInterop { return methodInfo.Invoke(targetInstance, suppliedArgs); } - catch (Exception ex) + catch (TargetInvocationException tie) when (tie.InnerException != null) { - throw UnwrapException(ex); + ExceptionDispatchInfo.Capture(tie.InnerException).Throw(); + throw null; // unreachable } } @@ -285,15 +282,5 @@ namespace Microsoft.JSInterop return loadedAssemblies.FirstOrDefault(a => a.GetName().Name.Equals(assemblyName, StringComparison.Ordinal)) ?? throw new ArgumentException($"There is no loaded assembly with the name '{assemblyName}'."); } - - private static Exception UnwrapException(Exception ex) - { - while ((ex is AggregateException || ex is TargetInvocationException) && ex.InnerException != null) - { - ex = ex.InnerException; - } - - return ex; - } } } diff --git a/src/JSInterop/Microsoft.JSInterop/src/JSRuntimeBase.cs b/src/JSInterop/Microsoft.JSInterop/src/JSRuntimeBase.cs index 09379396cf..d18bc7f4fe 100644 --- a/src/JSInterop/Microsoft.JSInterop/src/JSRuntimeBase.cs +++ b/src/JSInterop/Microsoft.JSInterop/src/JSRuntimeBase.cs @@ -3,6 +3,7 @@ using System; using System.Collections.Concurrent; +using System.Runtime.ExceptionServices; using System.Threading; using System.Threading.Tasks; @@ -80,6 +81,10 @@ namespace Microsoft.JSInterop { resultOrException = resultOrException.ToString(); } + else if (!success && resultOrException is ExceptionDispatchInfo edi) + { + resultOrException = edi.SourceException.ToString(); + } // We pass 0 as the async handle because we don't want the JS-side code to // send back any notification (we're just providing a result for an existing async call) diff --git a/src/JSInterop/Microsoft.JSInterop/test/DotNetDispatcherTest.cs b/src/JSInterop/Microsoft.JSInterop/test/DotNetDispatcherTest.cs index 93ef9a2498..e3f91a6fd0 100644 --- a/src/JSInterop/Microsoft.JSInterop/test/DotNetDispatcherTest.cs +++ b/src/JSInterop/Microsoft.JSInterop/test/DotNetDispatcherTest.cs @@ -285,6 +285,54 @@ namespace Microsoft.JSInterop.Tests Assert.Equal(2468, resultDto2.IntVal); }); + + [Fact] + public Task CanInvokeSyncThrowingMethod() => WithJSRuntime(async jsRuntime => + { + // Arrange + + // Act + var callId = "123"; + var resultTask = jsRuntime.NextInvocationTask; + DotNetDispatcher.BeginInvoke(callId, thisAssemblyName, nameof(ThrowingClass.ThrowingMethod), default, default); + + await resultTask; // This won't throw, it sets properties on the jsRuntime. + + // Assert + var result = Json.Deserialize(jsRuntime.LastInvocationArgsJson); + Assert.Equal(callId, result[0]); + Assert.False((bool)result[1]); // Fails + + // Make sure the method that threw the exception shows up in the call stack + // https://github.com/aspnet/AspNetCore/issues/8612 + var exception = (string)result[2]; + Assert.Contains(nameof(ThrowingClass.ThrowingMethod), exception); + }); + + [Fact] + public Task CanInvokeAsyncThrowingMethod() => WithJSRuntime(async jsRuntime => + { + // Arrange + + // Act + var callId = "123"; + var resultTask = jsRuntime.NextInvocationTask; + DotNetDispatcher.BeginInvoke(callId, thisAssemblyName, nameof(ThrowingClass.AsyncThrowingMethod), default, default); + + await resultTask; // This won't throw, it sets properties on the jsRuntime. + + // Assert + var result = Json.Deserialize(jsRuntime.LastInvocationArgsJson); + Assert.Equal(callId, result[0]); + Assert.False((bool)result[1]); // Fails + + // Make sure the method that threw the exception shows up in the call stack + // https://github.com/aspnet/AspNetCore/issues/8612 + var exception = (string)result[2]; + Assert.Contains(nameof(ThrowingClass.AsyncThrowingMethod), exception); + }); + + Task WithJSRuntime(Action testCode) { return WithJSRuntime(jsRuntime => @@ -413,6 +461,22 @@ namespace Microsoft.JSInterop.Tests public int IntVal { get; set; } } + public class ThrowingClass + { + [JSInvokable] + public static string ThrowingMethod() + { + throw new InvalidTimeZoneException(); + } + + [JSInvokable] + public static async Task AsyncThrowingMethod() + { + await Task.Yield(); + throw new InvalidTimeZoneException(); + } + } + public class TestJSRuntime : JSInProcessRuntimeBase { private TaskCompletionSource _nextInvocationTcs = new TaskCompletionSource(); From 76e0fc251c104a6acc9f4d8f86463dfe8c2cea4e Mon Sep 17 00:00:00 2001 From: Justin Kotalik Date: Tue, 9 Apr 2019 14:03:12 -0700 Subject: [PATCH 0085/1101] Add Repeat attribute (dotnet/extensions#1375) \n\nCommit migrated from https://github.com/dotnet/extensions/commit/13a00b0557e51aefa8132781def19f334a829614 --- src/Testing/src/xunit/ConditionalFactAttribute.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Testing/src/xunit/ConditionalFactAttribute.cs b/src/Testing/src/xunit/ConditionalFactAttribute.cs index 7448b48d8c..ce37df2e56 100644 --- a/src/Testing/src/xunit/ConditionalFactAttribute.cs +++ b/src/Testing/src/xunit/ConditionalFactAttribute.cs @@ -12,4 +12,4 @@ namespace Microsoft.AspNetCore.Testing.xunit public class ConditionalFactAttribute : FactAttribute { } -} \ No newline at end of file +} From f57e591af3e3fd0500fb1e51b5be8a4fb5a3562a Mon Sep 17 00:00:00 2001 From: Justin Kotalik Date: Tue, 9 Apr 2019 14:03:12 -0700 Subject: [PATCH 0086/1101] Add Repeat attribute (#1375) --- src/Testing/src/LoggedTest/LoggedTestBase.cs | 2 +- src/Testing/test/LoggedTestXunitTests.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Testing/src/LoggedTest/LoggedTestBase.cs b/src/Testing/src/LoggedTest/LoggedTestBase.cs index 492de61cb6..94cdf82257 100644 --- a/src/Testing/src/LoggedTest/LoggedTestBase.cs +++ b/src/Testing/src/LoggedTest/LoggedTestBase.cs @@ -26,7 +26,7 @@ namespace Microsoft.Extensions.Logging.Testing // Internal for testing internal string ResolvedTestClassName { get; set; } - internal RetryContext RetryContext { get; set; } + internal RepeatContext RepeatContext { get; set; } public string ResolvedLogOutputDirectory { get; set; } diff --git a/src/Testing/test/LoggedTestXunitTests.cs b/src/Testing/test/LoggedTestXunitTests.cs index 507453a242..520ffaaa9e 100644 --- a/src/Testing/test/LoggedTestXunitTests.cs +++ b/src/Testing/test/LoggedTestXunitTests.cs @@ -1,4 +1,4 @@ -// Copyright (c) .NET Foundation. All rights reserved. +// 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.Linq; From 3ba58f028f538492797290c2a7876a0833dff762 Mon Sep 17 00:00:00 2001 From: Ryan Nowak Date: Thu, 11 Apr 2019 10:30:52 -0700 Subject: [PATCH 0087/1101] Port fix for dotnet/extensions#1041 to 2.2 (dotnet/extensions#1312) * Update HealthCheckPublisherOptions.cs This seems wrong. Presumably fixes: https://github.com/aspnet/Extensions/issues/1041 (cherry picked from commit dotnet/extensions@b3c88d78fe112bc3b2e272299156857a383edf5d) * Update patchconfig.props\n\nCommit migrated from https://github.com/dotnet/extensions/commit/1301f31b91687ec8e9a34777c1b3095e53ee129f --- .../HealthChecks/src/HealthCheckPublisherOptions.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/HealthChecks/HealthChecks/src/HealthCheckPublisherOptions.cs b/src/HealthChecks/HealthChecks/src/HealthCheckPublisherOptions.cs index 1313718af8..6b7c8c3365 100644 --- a/src/HealthChecks/HealthChecks/src/HealthCheckPublisherOptions.cs +++ b/src/HealthChecks/HealthChecks/src/HealthCheckPublisherOptions.cs @@ -60,7 +60,7 @@ namespace Microsoft.Extensions.Diagnostics.HealthChecks throw new ArgumentException($"The {nameof(Period)} must not be infinite.", nameof(value)); } - _delay = value; + _period = value; } } From 7f5f0191019d017f5e28707d92c1f3789526e165 Mon Sep 17 00:00:00 2001 From: David Fowler Date: Fri, 12 Apr 2019 23:21:12 -0700 Subject: [PATCH 0088/1101] Remove target invocation exceptions (dotnet/extensions#1413) - Use BindingFlags.DoNotWrapExceptions to invoke\n\nCommit migrated from https://github.com/dotnet/extensions/commit/0b1aa473a7a722cd3f0aab5166fbe9e5203f0582 --- src/Shared/ActivatorUtilities/ActivatorUtilities.cs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/Shared/ActivatorUtilities/ActivatorUtilities.cs b/src/Shared/ActivatorUtilities/ActivatorUtilities.cs index e2553ced1a..c88914ee28 100644 --- a/src/Shared/ActivatorUtilities/ActivatorUtilities.cs +++ b/src/Shared/ActivatorUtilities/ActivatorUtilities.cs @@ -403,16 +403,20 @@ namespace Microsoft.Extensions.Internal } } +#if NETCOREAPP3_0 + return _constructor.Invoke(BindingFlags.DoNotWrapExceptions, binder: null, parameters: _parameterValues, culture: null); +#else try { return _constructor.Invoke(_parameterValues); } - catch (TargetInvocationException ex) + catch (TargetInvocationException ex) when (ex.InnerException != null) { ExceptionDispatchInfo.Capture(ex.InnerException).Throw(); // The above line will always throw, but the compiler requires we throw explicitly. throw; } +#endif } } From f86f24b50a22105414b917e417f453992b986f3b Mon Sep 17 00:00:00 2001 From: viktorpeacock <44648849+viktorpeacock@users.noreply.github.com> Date: Wed, 17 Apr 2019 16:10:12 +0100 Subject: [PATCH 0089/1101] =?UTF-8?q?Tags=20defined=20during=20health=20ch?= =?UTF-8?q?eck=20registration=20are=20now=20available=20in=20th=E2=80=A6?= =?UTF-8?q?=20(dotnet/extensions#1434)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Tags defined during health check registration are now available in the health report entries. * Additional constructor that accepts tags. Original constructor now calls a new one. * Update reference assemblies \n\nCommit migrated from https://github.com/dotnet/extensions/commit/8eaeab2a69bcb91acfedc7847a345f8b64994d89 --- ...ealthChecks.Abstractions.netstandard2.0.cs | 2 ++ .../Abstractions/src/HealthReportEntry.cs | 25 ++++++++++++++++++- .../src/DefaultHealthCheckService.cs | 3 ++- .../test/DefaultHealthCheckServiceTest.cs | 12 ++++++--- 4 files changed, 37 insertions(+), 5 deletions(-) diff --git a/src/HealthChecks/Abstractions/ref/Microsoft.Extensions.Diagnostics.HealthChecks.Abstractions.netstandard2.0.cs b/src/HealthChecks/Abstractions/ref/Microsoft.Extensions.Diagnostics.HealthChecks.Abstractions.netstandard2.0.cs index 9ab497257e..b229ca0d53 100644 --- a/src/HealthChecks/Abstractions/ref/Microsoft.Extensions.Diagnostics.HealthChecks.Abstractions.netstandard2.0.cs +++ b/src/HealthChecks/Abstractions/ref/Microsoft.Extensions.Diagnostics.HealthChecks.Abstractions.netstandard2.0.cs @@ -47,11 +47,13 @@ namespace Microsoft.Extensions.Diagnostics.HealthChecks private object _dummy; private int _dummyPrimitive; public HealthReportEntry(Microsoft.Extensions.Diagnostics.HealthChecks.HealthStatus status, string description, System.TimeSpan duration, System.Exception exception, System.Collections.Generic.IReadOnlyDictionary data) { throw null; } + public HealthReportEntry(Microsoft.Extensions.Diagnostics.HealthChecks.HealthStatus status, string description, System.TimeSpan duration, System.Exception exception, System.Collections.Generic.IReadOnlyDictionary data, System.Collections.Generic.IEnumerable tags = null) { throw null; } public System.Collections.Generic.IReadOnlyDictionary Data { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } } public string Description { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } } public System.TimeSpan Duration { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } } public System.Exception Exception { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } } public Microsoft.Extensions.Diagnostics.HealthChecks.HealthStatus Status { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } } + public System.Collections.Generic.IEnumerable Tags { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } } } public enum HealthStatus { diff --git a/src/HealthChecks/Abstractions/src/HealthReportEntry.cs b/src/HealthChecks/Abstractions/src/HealthReportEntry.cs index 6e7d6c6b8e..043c1414a4 100644 --- a/src/HealthChecks/Abstractions/src/HealthReportEntry.cs +++ b/src/HealthChecks/Abstractions/src/HealthReportEntry.cs @@ -1,8 +1,9 @@ -// Copyright (c) .NET Foundation. All rights reserved. +// 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.Collections.Generic; +using System.Linq; namespace Microsoft.Extensions.Diagnostics.HealthChecks { @@ -23,14 +24,31 @@ namespace Microsoft.Extensions.Diagnostics.HealthChecks /// An representing the exception that was thrown when checking for status (if any). /// Additional key-value pairs describing the health of the component. public HealthReportEntry(HealthStatus status, string description, TimeSpan duration, Exception exception, IReadOnlyDictionary data) + : this(status, description, duration, exception, data, null) + { + } + + /// + /// Creates a new with the specified values for , , + /// , and . + /// + /// A value indicating the health status of the component that was checked. + /// A human-readable description of the status of the component that was checked. + /// A value indicating the health execution duration. + /// An representing the exception that was thrown when checking for status (if any). + /// Additional key-value pairs describing the health of the component. + /// Tags associated with the health check that generated the report entry. + public HealthReportEntry(HealthStatus status, string description, TimeSpan duration, Exception exception, IReadOnlyDictionary data, IEnumerable tags = null) { Status = status; Description = description; Duration = duration; Exception = exception; Data = data ?? _emptyReadOnlyDictionary; + Tags = tags ?? Enumerable.Empty(); } + /// /// Gets additional key-value pairs describing the health of the component. /// @@ -55,5 +73,10 @@ namespace Microsoft.Extensions.Diagnostics.HealthChecks /// Gets the health status of the component that was checked. /// public HealthStatus Status { get; } + + /// + /// Gets the tags associated with the health check. + /// + public IEnumerable Tags { get; } } } diff --git a/src/HealthChecks/HealthChecks/src/DefaultHealthCheckService.cs b/src/HealthChecks/HealthChecks/src/DefaultHealthCheckService.cs index c2c9084e0a..d7ce9edb9e 100644 --- a/src/HealthChecks/HealthChecks/src/DefaultHealthCheckService.cs +++ b/src/HealthChecks/HealthChecks/src/DefaultHealthCheckService.cs @@ -111,7 +111,8 @@ namespace Microsoft.Extensions.Diagnostics.HealthChecks description: result.Description, duration: duration, exception: result.Exception, - data: result.Data); + data: result.Data, + tags: registration.Tags); Log.HealthCheckEnd(_logger, registration, entry, duration); Log.HealthCheckData(_logger, registration, entry); diff --git a/src/HealthChecks/HealthChecks/test/DefaultHealthCheckServiceTest.cs b/src/HealthChecks/HealthChecks/test/DefaultHealthCheckServiceTest.cs index 38442edb93..50cf7ebeae 100644 --- a/src/HealthChecks/HealthChecks/test/DefaultHealthCheckServiceTest.cs +++ b/src/HealthChecks/HealthChecks/test/DefaultHealthCheckServiceTest.cs @@ -57,6 +57,9 @@ namespace Microsoft.Extensions.Diagnostics.HealthChecks const string UnhealthyMessage = "Halp!"; const string HealthyMessage = "Everything is A-OK"; var exception = new Exception("Things are pretty bad!"); + var healthyCheckTags = new List { "healthy-check-tag" }; + var degradedCheckTags = new List { "degraded-check-tag" }; + var unhealthyCheckTags = new List { "unhealthy-check-tag" }; // Arrange var data = new Dictionary() @@ -66,9 +69,9 @@ namespace Microsoft.Extensions.Diagnostics.HealthChecks var service = CreateHealthChecksService(b => { - b.AddAsyncCheck("HealthyCheck", _ => Task.FromResult(HealthCheckResult.Healthy(HealthyMessage, data))); - b.AddAsyncCheck("DegradedCheck", _ => Task.FromResult(HealthCheckResult.Degraded(DegradedMessage))); - b.AddAsyncCheck("UnhealthyCheck", _ => Task.FromResult(HealthCheckResult.Unhealthy(UnhealthyMessage, exception))); + b.AddAsyncCheck("HealthyCheck", _ => Task.FromResult(HealthCheckResult.Healthy(HealthyMessage, data)), healthyCheckTags); + b.AddAsyncCheck("DegradedCheck", _ => Task.FromResult(HealthCheckResult.Degraded(DegradedMessage)), degradedCheckTags); + b.AddAsyncCheck("UnhealthyCheck", _ => Task.FromResult(HealthCheckResult.Unhealthy(UnhealthyMessage, exception)), unhealthyCheckTags); }); // Act @@ -84,6 +87,7 @@ namespace Microsoft.Extensions.Diagnostics.HealthChecks Assert.Equal(HealthStatus.Degraded, actual.Value.Status); Assert.Null(actual.Value.Exception); Assert.Empty(actual.Value.Data); + Assert.Equal(actual.Value.Tags, degradedCheckTags); }, actual => { @@ -96,6 +100,7 @@ namespace Microsoft.Extensions.Diagnostics.HealthChecks Assert.Equal(DataKey, item.Key); Assert.Equal(DataValue, item.Value); }); + Assert.Equal(actual.Value.Tags, healthyCheckTags); }, actual => { @@ -104,6 +109,7 @@ namespace Microsoft.Extensions.Diagnostics.HealthChecks Assert.Equal(HealthStatus.Unhealthy, actual.Value.Status); Assert.Same(exception, actual.Value.Exception); Assert.Empty(actual.Value.Data); + Assert.Equal(actual.Value.Tags, unhealthyCheckTags); }); } From 188c45965a9677af9aa725e2f9eddfef7e211767 Mon Sep 17 00:00:00 2001 From: Andrew Stanton-Nurse Date: Fri, 19 Apr 2019 10:19:59 -0700 Subject: [PATCH 0090/1101] skip flaky attribute tests, they're just too annoying (dotnet/extensions#1476) \n\nCommit migrated from https://github.com/dotnet/extensions/commit/97bd3149886d12fad88c1405f5abf5a993ede996 --- src/Testing/test/FlakyAttributeTest.cs | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/src/Testing/test/FlakyAttributeTest.cs b/src/Testing/test/FlakyAttributeTest.cs index 7837bd8711..1b9a122d93 100644 --- a/src/Testing/test/FlakyAttributeTest.cs +++ b/src/Testing/test/FlakyAttributeTest.cs @@ -7,7 +7,7 @@ namespace Microsoft.AspNetCore.Testing.Tests { public class FlakyAttributeTest { - [Fact] + [Fact(Skip = "These tests are nice when you need them but annoying when on all the time.")] [Flaky("http://example.com", FlakyOn.All)] public void AlwaysFlakyInCI() { @@ -17,7 +17,7 @@ namespace Microsoft.AspNetCore.Testing.Tests } } - [Fact] + [Fact(Skip = "These tests are nice when you need them but annoying when on all the time.")] [Flaky("http://example.com", FlakyOn.Helix.All)] public void FlakyInHelixOnly() { @@ -27,7 +27,7 @@ namespace Microsoft.AspNetCore.Testing.Tests } } - [Fact] + [Fact(Skip = "These tests are nice when you need them but annoying when on all the time.")] [Flaky("http://example.com", FlakyOn.Helix.macOS1012Amd64, FlakyOn.Helix.Fedora28Amd64)] public void FlakyInSpecificHelixQueue() { @@ -43,7 +43,7 @@ namespace Microsoft.AspNetCore.Testing.Tests } } - [Fact] + [Fact(Skip = "These tests are nice when you need them but annoying when on all the time.")] [Flaky("http://example.com", FlakyOn.AzP.All)] public void FlakyInAzPOnly() { @@ -53,7 +53,7 @@ namespace Microsoft.AspNetCore.Testing.Tests } } - [Fact] + [Fact(Skip = "These tests are nice when you need them but annoying when on all the time.")] [Flaky("http://example.com", FlakyOn.AzP.Windows)] public void FlakyInAzPWindowsOnly() { @@ -63,7 +63,7 @@ namespace Microsoft.AspNetCore.Testing.Tests } } - [Fact] + [Fact(Skip = "These tests are nice when you need them but annoying when on all the time.")] [Flaky("http://example.com", FlakyOn.AzP.macOS)] public void FlakyInAzPmacOSOnly() { @@ -73,7 +73,7 @@ namespace Microsoft.AspNetCore.Testing.Tests } } - [Fact] + [Fact(Skip = "These tests are nice when you need them but annoying when on all the time.")] [Flaky("http://example.com", FlakyOn.AzP.Linux)] public void FlakyInAzPLinuxOnly() { @@ -83,7 +83,7 @@ namespace Microsoft.AspNetCore.Testing.Tests } } - [Fact] + [Fact(Skip = "These tests are nice when you need them but annoying when on all the time.")] [Flaky("http://example.com", FlakyOn.AzP.Linux, FlakyOn.AzP.macOS)] public void FlakyInAzPNonWindowsOnly() { From d1aa9055ce3a5c9ae0b7f80bf05f8efb1ae4119b Mon Sep 17 00:00:00 2001 From: "dotnet-maestro[bot]" Date: Mon, 22 Apr 2019 16:37:47 -0700 Subject: [PATCH 0091/1101] Update dependencies from dotnet/arcade and re-generate reference source code (dotnet/extensions#1458) \n\nCommit migrated from https://github.com/dotnet/extensions/commit/996c25be271fa2a42e535fcddec22471d746432f --- ...ions.Diagnostics.HealthChecks.Abstractions.netstandard2.0.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/HealthChecks/Abstractions/ref/Microsoft.Extensions.Diagnostics.HealthChecks.Abstractions.netstandard2.0.cs b/src/HealthChecks/Abstractions/ref/Microsoft.Extensions.Diagnostics.HealthChecks.Abstractions.netstandard2.0.cs index b229ca0d53..8c53adc275 100644 --- a/src/HealthChecks/Abstractions/ref/Microsoft.Extensions.Diagnostics.HealthChecks.Abstractions.netstandard2.0.cs +++ b/src/HealthChecks/Abstractions/ref/Microsoft.Extensions.Diagnostics.HealthChecks.Abstractions.netstandard2.0.cs @@ -57,9 +57,9 @@ namespace Microsoft.Extensions.Diagnostics.HealthChecks } public enum HealthStatus { + Unhealthy = 0, Degraded = 1, Healthy = 2, - Unhealthy = 0, } public partial interface IHealthCheck { From 3a1330b7acadbe0885d5e69db747adf48e2f1529 Mon Sep 17 00:00:00 2001 From: Nate McMaster Date: Fri, 3 May 2019 12:04:29 -0700 Subject: [PATCH 0092/1101] Enable resx source generation (dotnet/extensions#1635) \n\nCommit migrated from https://github.com/dotnet/extensions/commit/ad92508be6fd31d5c7b9b4f643382a14ab7ffbce --- .../src/Properties/Resources.Designer.cs | 62 ------------------- 1 file changed, 62 deletions(-) delete mode 100644 src/Localization/Localization/src/Properties/Resources.Designer.cs diff --git a/src/Localization/Localization/src/Properties/Resources.Designer.cs b/src/Localization/Localization/src/Properties/Resources.Designer.cs deleted file mode 100644 index 1123d648ad..0000000000 --- a/src/Localization/Localization/src/Properties/Resources.Designer.cs +++ /dev/null @@ -1,62 +0,0 @@ -// -namespace Microsoft.Extensions.Localization -{ - using System.Globalization; - using System.Reflection; - using System.Resources; - - internal static class Resources - { - private static readonly ResourceManager _resourceManager - = new ResourceManager("Microsoft.Extensions.Localization.Resources", typeof(Resources).GetTypeInfo().Assembly); - - /// - /// The manifest '{0}' was not found. - /// - internal static string Localization_MissingManifest - { - get { return GetString("Localization_MissingManifest"); } - } - - /// - /// The manifest '{0}' was not found. - /// - internal static string FormatLocalization_MissingManifest(object p0) - { - return string.Format(CultureInfo.CurrentCulture, GetString("Localization_MissingManifest"), p0); - } - - /// - /// No manifests exist for the current culture. - /// - internal static string Localization_MissingManifest_Parent - { - get { return GetString("Localization_MissingManifest_Parent"); } - } - - /// - /// No manifests exist for the current culture. - /// - internal static string FormatLocalization_MissingManifest_Parent() - { - return GetString("Localization_MissingManifest_Parent"); - } - - private static string GetString(string name, params string[] formatterNames) - { - var value = _resourceManager.GetString(name); - - System.Diagnostics.Debug.Assert(value != null); - - if (formatterNames != null) - { - for (var i = 0; i < formatterNames.Length; i++) - { - value = value.Replace("{" + formatterNames[i] + "}", "{" + i + "}"); - } - } - - return value; - } - } -} From 10e316f541ea0d6b3a941e41c62f09d5baabe329 Mon Sep 17 00:00:00 2001 From: Larry Ewing Date: Mon, 6 May 2019 17:38:50 -0500 Subject: [PATCH 0093/1101] Remove the exception filter that is problematic for wasm AOT \n\nCommit migrated from https://github.com/dotnet/extensions/commit/b3cdc7bd5d46a70ae2e0294c750da78c659f04fe --- .../Microsoft.JSInterop/src/DotNetDispatcher.cs | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/src/JSInterop/Microsoft.JSInterop/src/DotNetDispatcher.cs b/src/JSInterop/Microsoft.JSInterop/src/DotNetDispatcher.cs index ac936e670a..20904e1bf0 100644 --- a/src/JSInterop/Microsoft.JSInterop/src/DotNetDispatcher.cs +++ b/src/JSInterop/Microsoft.JSInterop/src/DotNetDispatcher.cs @@ -171,10 +171,15 @@ namespace Microsoft.JSInterop { return methodInfo.Invoke(targetInstance, suppliedArgs); } - catch (TargetInvocationException tie) when (tie.InnerException != null) + catch (TargetInvocationException tie) { - ExceptionDispatchInfo.Capture(tie.InnerException).Throw(); - throw null; // unreachable + if (tie.InnerException != null) + { + ExceptionDispatchInfo.Capture(tie.InnerException).Throw(); + throw null; // unreached + } + + throw; } } From 2706bc5610fdfb7bee8511702babc530c073bb10 Mon Sep 17 00:00:00 2001 From: Larry Ewing Date: Tue, 7 May 2019 18:00:45 -0500 Subject: [PATCH 0094/1101] Add comment about not using an exception filter \n\nCommit migrated from https://github.com/dotnet/extensions/commit/b6abd65ea27719ede2147a4a3ed6f4b226f11fd8 --- src/JSInterop/Microsoft.JSInterop/src/DotNetDispatcher.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/JSInterop/Microsoft.JSInterop/src/DotNetDispatcher.cs b/src/JSInterop/Microsoft.JSInterop/src/DotNetDispatcher.cs index 20904e1bf0..d6c83d512d 100644 --- a/src/JSInterop/Microsoft.JSInterop/src/DotNetDispatcher.cs +++ b/src/JSInterop/Microsoft.JSInterop/src/DotNetDispatcher.cs @@ -171,7 +171,7 @@ namespace Microsoft.JSInterop { return methodInfo.Invoke(targetInstance, suppliedArgs); } - catch (TargetInvocationException tie) + catch (TargetInvocationException tie) // Avoid using exception filters for AOT runtime support { if (tie.InnerException != null) { @@ -243,8 +243,8 @@ namespace Microsoft.JSInterop var invokableMethods = GetRequiredLoadedAssembly(assemblyName) .GetExportedTypes() .SelectMany(type => type.GetMethods( - BindingFlags.Public | - BindingFlags.DeclaredOnly | + BindingFlags.Public | + BindingFlags.DeclaredOnly | BindingFlags.Instance | BindingFlags.Static)) .Where(method => method.IsDefined(typeof(JSInvokableAttribute), inherit: false)); From 213076ff4b84bb9da4c4cdc56d448531a9d10d41 Mon Sep 17 00:00:00 2001 From: Nate McMaster Date: Tue, 21 May 2019 14:18:22 -0700 Subject: [PATCH 0095/1101] Obsolete TestPathUtilities (dotnet/extensions#1698) \n\nCommit migrated from https://github.com/dotnet/extensions/commit/da5dee978f4eece3de85b776dfcf9dcc4cf5b109 --- src/Testing/src/TestPathUtilities.cs | 1 + src/Testing/test/TestPathUtilitiesTest.cs | 4 ++++ 2 files changed, 5 insertions(+) diff --git a/src/Testing/src/TestPathUtilities.cs b/src/Testing/src/TestPathUtilities.cs index f982471f39..a5d83feeff 100644 --- a/src/Testing/src/TestPathUtilities.cs +++ b/src/Testing/src/TestPathUtilities.cs @@ -6,6 +6,7 @@ using System.IO; namespace Microsoft.AspNetCore.Testing { + [Obsolete("This API is obsolete and the pattern its usage encouraged should not be used anymore. See https://github.com/aspnet/Extensions/issues/1697 for details.")] public class TestPathUtilities { public static string GetRepoRootDirectory() diff --git a/src/Testing/test/TestPathUtilitiesTest.cs b/src/Testing/test/TestPathUtilitiesTest.cs index c77194a548..4a6a74ae9c 100644 --- a/src/Testing/test/TestPathUtilitiesTest.cs +++ b/src/Testing/test/TestPathUtilitiesTest.cs @@ -9,6 +9,9 @@ namespace Microsoft.AspNetCore.Testing { public class TestPathUtilitiesTest { + // Entire test pending removal - see https://github.com/aspnet/Extensions/issues/1697 +#pragma warning disable 0618 + [Fact] public void GetSolutionRootDirectory_ResolvesSolutionRoot() { @@ -27,5 +30,6 @@ namespace Microsoft.AspNetCore.Testing var exception = Assert.Throws(() => TestPathUtilities.GetSolutionRootDirectory("NotTesting")); Assert.Equal($"Solution file NotTesting.sln could not be found in {AppContext.BaseDirectory} or its parent directories.", exception.Message); } +#pragma warning restore 0618 } } From cfc1320ee245f442460ec8084260807fe2fdd142 Mon Sep 17 00:00:00 2001 From: Nate McMaster Date: Thu, 23 May 2019 16:33:55 -0700 Subject: [PATCH 0096/1101] Add support for source-build (dotnet/extensions#1740) \n\nCommit migrated from https://github.com/dotnet/extensions/commit/ad4067b012f62a70bb7378c3e0a029168f3eb106 --- src/Testing/Directory.Build.props | 8 ++++++++ 1 file changed, 8 insertions(+) create mode 100644 src/Testing/Directory.Build.props diff --git a/src/Testing/Directory.Build.props b/src/Testing/Directory.Build.props new file mode 100644 index 0000000000..68f87d4f24 --- /dev/null +++ b/src/Testing/Directory.Build.props @@ -0,0 +1,8 @@ + + + + + + true + + From 8b068f23201339b2cbf806c10501d794763f5060 Mon Sep 17 00:00:00 2001 From: Nate McMaster Date: Tue, 28 May 2019 18:07:58 -0700 Subject: [PATCH 0097/1101] Build and publish NPM packages in CI (dotnet/extensions#1748) \n\nCommit migrated from https://github.com/dotnet/extensions/commit/92b2c76bc8bb082e49bc7549e4d95ab244bd6975 --- src/JSInterop/Microsoft.JSInterop.JS/src/package.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/JSInterop/Microsoft.JSInterop.JS/src/package.json b/src/JSInterop/Microsoft.JSInterop.JS/src/package.json index 8be950e16f..803e02a35b 100644 --- a/src/JSInterop/Microsoft.JSInterop.JS/src/package.json +++ b/src/JSInterop/Microsoft.JSInterop.JS/src/package.json @@ -12,12 +12,12 @@ }, "repository": { "type": "git", - "url": "git+https://github.com/aspnet/AspNetCore.git" + "url": "git+https://github.com/aspnet/Extensions.git" }, "author": "Microsoft", "license": "Apache-2.0", "bugs": { - "url": "https://github.com/aspnet/AspNetCore/issues" + "url": "https://github.com/aspnet/Extensions/issues" }, "homepage": "https://github.com/aspnet/Extensions/tree/master/src/JSInterop#readme", "files": [ From 78daf0166c82aabff2762247c64d86a7096aa80e Mon Sep 17 00:00:00 2001 From: Pranav K Date: Wed, 29 May 2019 09:47:26 -0700 Subject: [PATCH 0098/1101] Use System.Text.Json in Microsoft.JSInterop (dotnet/extensions#1704) * Use System.Text.Json in Microsoft.JSInterop \n\nCommit migrated from https://github.com/dotnet/extensions/commit/f6c9258abe10d6e79ab76b5b8217b0d2b892aa0e --- .../src/src/Microsoft.JSInterop.ts | 11 +- .../ref/Microsoft.JSInterop.csproj | 2 +- .../ref/Microsoft.JSInterop.netstandard2.0.cs | 35 +- .../src/DotNetDispatcher.cs | 214 +- .../src/DotNetObjectRef.cs | 59 +- .../src/DotNetObjectRefManager.cs | 51 + .../src/DotNetObjectRefOfT.cs | 76 + .../src/ICustomArgSerializer.cs | 22 - .../src/IDotNetObjectRef.cs | 12 + .../Microsoft.JSInterop/src/IJSRuntime.cs | 17 +- .../src/InteropArgSerializerStrategy.cs | 121 - .../src/JSAsyncCallResult.cs | 20 +- .../src/JSInProcessRuntimeBase.cs | 17 +- .../Microsoft.JSInterop/src/JSRuntimeBase.cs | 61 +- .../Microsoft.JSInterop/src/Json/CamelCase.cs | 59 - .../Microsoft.JSInterop/src/Json/Json.cs | 39 - .../src/Json/SimpleJson/README.txt | 24 - .../src/Json/SimpleJson/SimpleJson.cs | 2201 ----------------- .../src/JsonSerializerOptionsProvider.cs | 15 + .../src/Microsoft.JSInterop.csproj | 6 +- .../test/DotNetDispatcherTest.cs | 162 +- .../test/DotNetObjectRefTest.cs | 61 +- .../test/JSInProcessRuntimeBaseTest.cs | 35 +- .../test/JSRuntimeBaseTest.cs | 59 +- .../Microsoft.JSInterop/test/JSRuntimeTest.cs | 3 - .../Microsoft.JSInterop/test/JsonUtilTest.cs | 349 --- 26 files changed, 550 insertions(+), 3181 deletions(-) create mode 100644 src/JSInterop/Microsoft.JSInterop/src/DotNetObjectRefManager.cs create mode 100644 src/JSInterop/Microsoft.JSInterop/src/DotNetObjectRefOfT.cs delete mode 100644 src/JSInterop/Microsoft.JSInterop/src/ICustomArgSerializer.cs create mode 100644 src/JSInterop/Microsoft.JSInterop/src/IDotNetObjectRef.cs delete mode 100644 src/JSInterop/Microsoft.JSInterop/src/InteropArgSerializerStrategy.cs delete mode 100644 src/JSInterop/Microsoft.JSInterop/src/Json/CamelCase.cs delete mode 100644 src/JSInterop/Microsoft.JSInterop/src/Json/Json.cs delete mode 100644 src/JSInterop/Microsoft.JSInterop/src/Json/SimpleJson/README.txt delete mode 100644 src/JSInterop/Microsoft.JSInterop/src/Json/SimpleJson/SimpleJson.cs create mode 100644 src/JSInterop/Microsoft.JSInterop/src/JsonSerializerOptionsProvider.cs delete mode 100644 src/JSInterop/Microsoft.JSInterop/test/JsonUtilTest.cs diff --git a/src/JSInterop/Microsoft.JSInterop.JS/src/src/Microsoft.JSInterop.ts b/src/JSInterop/Microsoft.JSInterop.JS/src/src/Microsoft.JSInterop.ts index 4b5d409d0f..5386b92bb5 100644 --- a/src/JSInterop/Microsoft.JSInterop.JS/src/src/Microsoft.JSInterop.ts +++ b/src/JSInterop/Microsoft.JSInterop.JS/src/src/Microsoft.JSInterop.ts @@ -264,17 +264,14 @@ module DotNet { } public serializeAsArg() { - return `__dotNetObject:${this._id}`; + return {__dotNetObject: this._id}; } } - const dotNetObjectValueFormat = /^__dotNetObject\:(\d+)$/; + const dotNetObjectRefKey = '__dotNetObject'; attachReviver(function reviveDotNetObject(key: any, value: any) { - if (typeof value === 'string') { - const match = value.match(dotNetObjectValueFormat); - if (match) { - return new DotNetObject(parseInt(match[1])); - } + if (value && typeof value === 'object' && value.hasOwnProperty(dotNetObjectRefKey)) { + return new DotNetObject(value.__dotNetObject); } // Unrecognized - let another reviver handle it diff --git a/src/JSInterop/Microsoft.JSInterop/ref/Microsoft.JSInterop.csproj b/src/JSInterop/Microsoft.JSInterop/ref/Microsoft.JSInterop.csproj index 87fd913427..5f83c53091 100644 --- a/src/JSInterop/Microsoft.JSInterop/ref/Microsoft.JSInterop.csproj +++ b/src/JSInterop/Microsoft.JSInterop/ref/Microsoft.JSInterop.csproj @@ -5,6 +5,6 @@ - + diff --git a/src/JSInterop/Microsoft.JSInterop/ref/Microsoft.JSInterop.netstandard2.0.cs b/src/JSInterop/Microsoft.JSInterop/ref/Microsoft.JSInterop.netstandard2.0.cs index 95b9b7956c..1db3385bd7 100644 --- a/src/JSInterop/Microsoft.JSInterop/ref/Microsoft.JSInterop.netstandard2.0.cs +++ b/src/JSInterop/Microsoft.JSInterop/ref/Microsoft.JSInterop.netstandard2.0.cs @@ -12,12 +12,19 @@ namespace Microsoft.JSInterop [Microsoft.JSInterop.JSInvokableAttribute("DotNetDispatcher.ReleaseDotNetObject")] public static void ReleaseDotNetObject(long dotNetObjectId) { } } - public partial class DotNetObjectRef : System.IDisposable + public static partial class DotNetObjectRef { - public DotNetObjectRef(object value) { } - public object Value { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } } + public static Microsoft.JSInterop.DotNetObjectRef Create(TValue value) where TValue : class { throw null; } + } + public sealed partial class DotNetObjectRef : System.IDisposable where TValue : class + { + [System.ComponentModel.EditorBrowsableAttribute(System.ComponentModel.EditorBrowsableState.Never)] + public DotNetObjectRef() { } + [System.Text.Json.Serialization.JsonIgnoreAttribute] + public TValue Value { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } } + [System.ComponentModel.EditorBrowsableAttribute(System.ComponentModel.EditorBrowsableState.Never)] + public long __dotNetObject { get { throw null; } set { } } public void Dispose() { } - public void EnsureAttachedToJsRuntime(Microsoft.JSInterop.IJSRuntime runtime) { } } public partial interface IJSInProcessRuntime : Microsoft.JSInterop.IJSRuntime { @@ -25,8 +32,7 @@ namespace Microsoft.JSInterop } public partial interface IJSRuntime { - System.Threading.Tasks.Task InvokeAsync(string identifier, params object[] args); - void UntrackObjectRef(Microsoft.JSInterop.DotNetObjectRef dotNetObjectRef); + System.Threading.Tasks.Task InvokeAsync(string identifier, params object[] args); } public partial class JSException : System.Exception { @@ -36,7 +42,7 @@ namespace Microsoft.JSInterop { protected JSInProcessRuntimeBase() { } protected abstract string InvokeJS(string identifier, string argsJson); - public T Invoke(string identifier, params object[] args) { throw null; } + public TValue Invoke(string identifier, params object[] args) { throw null; } } [System.AttributeUsageAttribute(System.AttributeTargets.Method, AllowMultiple=true)] public partial class JSInvokableAttribute : System.Attribute @@ -45,30 +51,21 @@ namespace Microsoft.JSInterop public JSInvokableAttribute(string identifier) { } public string Identifier { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } } } - public static partial class Json - { - public static T Deserialize(string json) { throw null; } - public static string Serialize(object value) { throw null; } - } public static partial class JSRuntime { public static void SetCurrentJSRuntime(Microsoft.JSInterop.IJSRuntime instance) { } } public abstract partial class JSRuntimeBase : Microsoft.JSInterop.IJSRuntime { - public JSRuntimeBase() { } + protected JSRuntimeBase() { } protected abstract void BeginInvokeJS(long asyncHandle, string identifier, string argsJson); public System.Threading.Tasks.Task InvokeAsync(string identifier, params object[] args) { throw null; } - public void UntrackObjectRef(Microsoft.JSInterop.DotNetObjectRef dotNetObjectRef) { } } } namespace Microsoft.JSInterop.Internal { - public partial interface ICustomArgSerializer - { - object ToJsonPrimitive(); - } - public partial class JSAsyncCallResult + [System.ComponentModel.EditorBrowsableAttribute(System.ComponentModel.EditorBrowsableState.Never)] + public sealed partial class JSAsyncCallResult { internal JSAsyncCallResult() { } } diff --git a/src/JSInterop/Microsoft.JSInterop/src/DotNetDispatcher.cs b/src/JSInterop/Microsoft.JSInterop/src/DotNetDispatcher.cs index d6c83d512d..5ecd3506e0 100644 --- a/src/JSInterop/Microsoft.JSInterop/src/DotNetDispatcher.cs +++ b/src/JSInterop/Microsoft.JSInterop/src/DotNetDispatcher.cs @@ -1,14 +1,16 @@ // 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 Microsoft.JSInterop.Internal; using System; using System.Collections.Concurrent; using System.Collections.Generic; using System.Linq; using System.Reflection; using System.Runtime.ExceptionServices; +using System.Text.Json; +using System.Text.Json.Serialization; using System.Threading.Tasks; +using Microsoft.JSInterop.Internal; namespace Microsoft.JSInterop { @@ -17,8 +19,10 @@ namespace Microsoft.JSInterop /// public static class DotNetDispatcher { - private static ConcurrentDictionary> _cachedMethodsByAssembly - = new ConcurrentDictionary>(); + internal const string DotNetObjectRefKey = nameof(DotNetObjectRef.__dotNetObject); + + private static readonly ConcurrentDictionary> _cachedMethodsByAssembly + = new ConcurrentDictionary>(); /// /// Receives a call from JS to .NET, locating and invoking the specified method. @@ -35,17 +39,19 @@ namespace Microsoft.JSInterop // the targeted method has [JSInvokable]. It is not itself subject to that restriction, // because there would be nobody to police that. This method *is* the police. - // DotNetDispatcher only works with JSRuntimeBase instances. - var jsRuntime = (JSRuntimeBase)JSRuntime.Current; - var targetInstance = (object)null; if (dotNetObjectId != default) { - targetInstance = jsRuntime.ArgSerializerStrategy.FindDotNetObject(dotNetObjectId); + targetInstance = DotNetObjectRefManager.Current.FindDotNetObject(dotNetObjectId); } var syncResult = InvokeSynchronously(assemblyName, methodIdentifier, targetInstance, argsJson); - return syncResult == null ? null : Json.Serialize(syncResult, jsRuntime.ArgSerializerStrategy); + if (syncResult == null) + { + return null; + } + + return JsonSerializer.ToString(syncResult, JsonSerializerOptionsProvider.Options); } /// @@ -69,9 +75,11 @@ namespace Microsoft.JSInterop // code has to implement its own way of returning async results. var jsRuntimeBaseInstance = (JSRuntimeBase)JSRuntime.Current; - var targetInstance = dotNetObjectId == default - ? null - : jsRuntimeBaseInstance.ArgSerializerStrategy.FindDotNetObject(dotNetObjectId); + var targetInstance = (object)null; + if (dotNetObjectId != default) + { + targetInstance = DotNetObjectRefManager.Current.FindDotNetObject(dotNetObjectId); + } // Using ExceptionDispatchInfo here throughout because we want to always preserve // original stack traces. @@ -121,6 +129,7 @@ namespace Microsoft.JSInterop private static object InvokeSynchronously(string assemblyName, string methodIdentifier, object targetInstance, string argsJson) { + AssemblyKey assemblyKey; if (targetInstance != null) { if (assemblyName != null) @@ -128,44 +137,16 @@ namespace Microsoft.JSInterop throw new ArgumentException($"For instance method calls, '{nameof(assemblyName)}' should be null. Value received: '{assemblyName}'."); } - assemblyName = targetInstance.GetType().Assembly.GetName().Name; + assemblyKey = new AssemblyKey(targetInstance.GetType().Assembly); + } + else + { + assemblyKey = new AssemblyKey(assemblyName); } - var (methodInfo, parameterTypes) = GetCachedMethodInfo(assemblyName, methodIdentifier); + var (methodInfo, parameterTypes) = GetCachedMethodInfo(assemblyKey, methodIdentifier); - // There's no direct way to say we want to deserialize as an array with heterogenous - // entry types (e.g., [string, int, bool]), so we need to deserialize in two phases. - // First we deserialize as object[], for which SimpleJson will supply JsonObject - // instances for nonprimitive values. - var suppliedArgs = (object[])null; - var suppliedArgsLength = 0; - if (argsJson != null) - { - suppliedArgs = Json.Deserialize(argsJson).ToArray(); - suppliedArgsLength = suppliedArgs.Length; - } - if (suppliedArgsLength != parameterTypes.Length) - { - throw new ArgumentException($"In call to '{methodIdentifier}', expected {parameterTypes.Length} parameters but received {suppliedArgsLength}."); - } - - // Second, convert each supplied value to the type expected by the method - var runtime = (JSRuntimeBase)JSRuntime.Current; - var serializerStrategy = runtime.ArgSerializerStrategy; - for (var i = 0; i < suppliedArgsLength; i++) - { - if (parameterTypes[i] == typeof(JSAsyncCallResult)) - { - // For JS async call results, we have to defer the deserialization until - // later when we know what type it's meant to be deserialized as - suppliedArgs[i] = new JSAsyncCallResult(suppliedArgs[i]); - } - else - { - suppliedArgs[i] = serializerStrategy.DeserializeObject( - suppliedArgs[i], parameterTypes[i]); - } - } + var suppliedArgs = ParseArguments(methodIdentifier, argsJson, parameterTypes); try { @@ -183,6 +164,85 @@ namespace Microsoft.JSInterop } } + private static object[] ParseArguments(string methodIdentifier, string argsJson, Type[] parameterTypes) + { + if (parameterTypes.Length == 0) + { + return Array.Empty(); + } + + // There's no direct way to say we want to deserialize as an array with heterogenous + // entry types (e.g., [string, int, bool]), so we need to deserialize in two phases. + var jsonDocument = JsonDocument.Parse(argsJson); + var shouldDisposeJsonDocument = true; + try + { + if (jsonDocument.RootElement.Type != JsonValueType.Array) + { + throw new ArgumentException($"Expected a JSON array but got {jsonDocument.RootElement.Type}."); + } + + var suppliedArgsLength = jsonDocument.RootElement.GetArrayLength(); + + if (suppliedArgsLength != parameterTypes.Length) + { + throw new ArgumentException($"In call to '{methodIdentifier}', expected {parameterTypes.Length} parameters but received {suppliedArgsLength}."); + } + + // Second, convert each supplied value to the type expected by the method + var suppliedArgs = new object[parameterTypes.Length]; + var index = 0; + foreach (var item in jsonDocument.RootElement.EnumerateArray()) + { + var parameterType = parameterTypes[index]; + + if (parameterType == typeof(JSAsyncCallResult)) + { + // We will pass the JsonDocument instance to JAsyncCallResult and make JSRuntimeBase + // responsible for disposing it. + shouldDisposeJsonDocument = false; + // For JS async call results, we have to defer the deserialization until + // later when we know what type it's meant to be deserialized as + suppliedArgs[index] = new JSAsyncCallResult(jsonDocument, item); + } + else if (IsIncorrectDotNetObjectRefUse(item, parameterType)) + { + throw new InvalidOperationException($"In call to '{methodIdentifier}', parameter of type '{parameterType.Name}' at index {(index + 1)} must be declared as type 'DotNetObjectRef<{parameterType.Name}>' to receive the incoming value."); + } + else + { + suppliedArgs[index] = JsonSerializer.Parse(item.GetRawText(), parameterType, JsonSerializerOptionsProvider.Options); + } + + index++; + } + + if (shouldDisposeJsonDocument) + { + jsonDocument.Dispose(); + } + + return suppliedArgs; + } + catch + { + // Always dispose the JsonDocument in case of an error. + jsonDocument.Dispose(); + throw; + } + + + static bool IsIncorrectDotNetObjectRefUse(JsonElement item, Type parameterType) + { + // Check for incorrect use of DotNetObjectRef at the top level. We know it's + // an incorrect use if there's a object that looks like { '__dotNetObject': }, + // but we aren't assigning to DotNetObjectRef{T}. + return item.Type == JsonValueType.Object && + item.TryGetProperty(DotNetObjectRefKey, out _) && + !typeof(IDotNetObjectRef).IsAssignableFrom(parameterType); + } + } + /// /// Receives notification that a call from .NET to JS has finished, marking the /// associated as completed. @@ -192,7 +252,7 @@ namespace Microsoft.JSInterop /// If is true, specifies the invocation result. If is false, gives the corresponding to the invocation failure. [JSInvokable(nameof(DotNetDispatcher) + "." + nameof(EndInvoke))] public static void EndInvoke(long asyncHandle, bool succeeded, JSAsyncCallResult result) - => ((JSRuntimeBase)JSRuntime.Current).EndInvokeJS(asyncHandle, succeeded, result.ResultOrException); + => ((JSRuntimeBase)JSRuntime.Current).EndInvokeJS(asyncHandle, succeeded, result); /// /// Releases the reference to the specified .NET object. This allows the .NET runtime @@ -207,16 +267,14 @@ namespace Microsoft.JSInterop [JSInvokable(nameof(DotNetDispatcher) + "." + nameof(ReleaseDotNetObject))] public static void ReleaseDotNetObject(long dotNetObjectId) { - // DotNetDispatcher only works with JSRuntimeBase instances. - var jsRuntime = (JSRuntimeBase)JSRuntime.Current; - jsRuntime.ArgSerializerStrategy.ReleaseDotNetObject(dotNetObjectId); + DotNetObjectRefManager.Current.ReleaseDotNetObject(dotNetObjectId); } - private static (MethodInfo, Type[]) GetCachedMethodInfo(string assemblyName, string methodIdentifier) + private static (MethodInfo, Type[]) GetCachedMethodInfo(AssemblyKey assemblyKey, string methodIdentifier) { - if (string.IsNullOrWhiteSpace(assemblyName)) + if (string.IsNullOrWhiteSpace(assemblyKey.AssemblyName)) { - throw new ArgumentException("Cannot be null, empty, or whitespace.", nameof(assemblyName)); + throw new ArgumentException("Cannot be null, empty, or whitespace.", nameof(assemblyKey.AssemblyName)); } if (string.IsNullOrWhiteSpace(methodIdentifier)) @@ -224,23 +282,23 @@ namespace Microsoft.JSInterop throw new ArgumentException("Cannot be null, empty, or whitespace.", nameof(methodIdentifier)); } - var assemblyMethods = _cachedMethodsByAssembly.GetOrAdd(assemblyName, ScanAssemblyForCallableMethods); + var assemblyMethods = _cachedMethodsByAssembly.GetOrAdd(assemblyKey, ScanAssemblyForCallableMethods); if (assemblyMethods.TryGetValue(methodIdentifier, out var result)) { return result; } else { - throw new ArgumentException($"The assembly '{assemblyName}' does not contain a public method with [{nameof(JSInvokableAttribute)}(\"{methodIdentifier}\")]."); + throw new ArgumentException($"The assembly '{assemblyKey.AssemblyName}' does not contain a public method with [{nameof(JSInvokableAttribute)}(\"{methodIdentifier}\")]."); } } - private static IReadOnlyDictionary ScanAssemblyForCallableMethods(string assemblyName) + private static Dictionary ScanAssemblyForCallableMethods(AssemblyKey assemblyKey) { // TODO: Consider looking first for assembly-level attributes (i.e., if there are any, // only use those) to avoid scanning, especially for framework assemblies. - var result = new Dictionary(); - var invokableMethods = GetRequiredLoadedAssembly(assemblyName) + var result = new Dictionary(StringComparer.Ordinal); + var invokableMethods = GetRequiredLoadedAssembly(assemblyKey) .GetExportedTypes() .SelectMany(type => type.GetMethods( BindingFlags.Public | @@ -261,7 +319,7 @@ namespace Microsoft.JSInterop { if (result.ContainsKey(identifier)) { - throw new InvalidOperationException($"The assembly '{assemblyName}' contains more than one " + + throw new InvalidOperationException($"The assembly '{assemblyKey.AssemblyName}' contains more than one " + $"[JSInvokable] method with identifier '{identifier}'. All [JSInvokable] methods within the same " + $"assembly must have different identifiers. You can pass a custom identifier as a parameter to " + $"the [JSInvokable] attribute."); @@ -276,7 +334,7 @@ namespace Microsoft.JSInterop return result; } - private static Assembly GetRequiredLoadedAssembly(string assemblyName) + private static Assembly GetRequiredLoadedAssembly(AssemblyKey assemblyKey) { // We don't want to load assemblies on demand here, because we don't necessarily trust // "assemblyName" to be something the developer intended to load. So only pick from the @@ -284,8 +342,40 @@ namespace Microsoft.JSInterop // In some edge cases this might force developers to explicitly call something on the // target assembly (from .NET) before they can invoke its allowed methods from JS. var loadedAssemblies = AppDomain.CurrentDomain.GetAssemblies(); - return loadedAssemblies.FirstOrDefault(a => a.GetName().Name.Equals(assemblyName, StringComparison.Ordinal)) - ?? throw new ArgumentException($"There is no loaded assembly with the name '{assemblyName}'."); + return loadedAssemblies.FirstOrDefault(a => new AssemblyKey(a).Equals(assemblyKey)) + ?? throw new ArgumentException($"There is no loaded assembly with the name '{assemblyKey.AssemblyName}'."); } + + private readonly struct AssemblyKey : IEquatable + { + public AssemblyKey(Assembly assembly) + { + Assembly = assembly; + AssemblyName = assembly.GetName().Name; + } + + public AssemblyKey(string assemblyName) + { + Assembly = null; + AssemblyName = assemblyName; + } + + public Assembly Assembly { get; } + + public string AssemblyName { get; } + + public bool Equals(AssemblyKey other) + { + if (Assembly != null && other.Assembly != null) + { + return Assembly == other.Assembly; + } + + return AssemblyName.Equals(other.AssemblyName, StringComparison.Ordinal); + } + + public override int GetHashCode() => StringComparer.Ordinal.GetHashCode(AssemblyName); + } + } } diff --git a/src/JSInterop/Microsoft.JSInterop/src/DotNetObjectRef.cs b/src/JSInterop/Microsoft.JSInterop/src/DotNetObjectRef.cs index aa62bee341..1aabc5ad59 100644 --- a/src/JSInterop/Microsoft.JSInterop/src/DotNetObjectRef.cs +++ b/src/JSInterop/Microsoft.JSInterop/src/DotNetObjectRef.cs @@ -1,66 +1,21 @@ // 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.Threading; - namespace Microsoft.JSInterop { /// - /// Wraps a JS interop argument, indicating that the value should not be serialized as JSON - /// but instead should be passed as a reference. - /// - /// To avoid leaking memory, the reference must later be disposed by JS code or by .NET code. + /// Provides convenience methods to produce a . /// - public class DotNetObjectRef : IDisposable + public static class DotNetObjectRef { /// - /// Gets the object instance represented by this wrapper. + /// Creates a new instance of . /// - public object Value { get; } - - // We track an associated IJSRuntime purely so that this class can be IDisposable - // in the normal way. Developers are more likely to use objectRef.Dispose() than - // some less familiar API such as JSRuntime.Current.UntrackObjectRef(objectRef). - private IJSRuntime _attachedToRuntime; - - /// - /// Constructs an instance of . - /// - /// The value being wrapped. - public DotNetObjectRef(object value) + /// The reference type to track. + /// An instance of . + public static DotNetObjectRef Create(TValue value) where TValue : class { - Value = value; - } - - /// - /// Ensures the is associated with the specified . - /// Developers do not normally need to invoke this manually, since it is called automatically by - /// framework code. - /// - /// The . - public void EnsureAttachedToJsRuntime(IJSRuntime runtime) - { - // The reason we populate _attachedToRuntime here rather than in the constructor - // is to ensure developers can't accidentally try to reuse DotNetObjectRef across - // different IJSRuntime instances. This method gets called as part of serializing - // the DotNetObjectRef during an interop call. - - var existingRuntime = Interlocked.CompareExchange(ref _attachedToRuntime, runtime, null); - if (existingRuntime != null && existingRuntime != runtime) - { - throw new InvalidOperationException($"The {nameof(DotNetObjectRef)} is already associated with a different {nameof(IJSRuntime)}. Do not attempt to re-use {nameof(DotNetObjectRef)} instances with multiple {nameof(IJSRuntime)} instances."); - } - } - - /// - /// Stops tracking this object reference, allowing it to be garbage collected - /// (if there are no other references to it). Once the instance is disposed, it - /// can no longer be used in interop calls from JavaScript code. - /// - public void Dispose() - { - _attachedToRuntime?.UntrackObjectRef(this); + return new DotNetObjectRef(value); } } } diff --git a/src/JSInterop/Microsoft.JSInterop/src/DotNetObjectRefManager.cs b/src/JSInterop/Microsoft.JSInterop/src/DotNetObjectRefManager.cs new file mode 100644 index 0000000000..f263716f53 --- /dev/null +++ b/src/JSInterop/Microsoft.JSInterop/src/DotNetObjectRefManager.cs @@ -0,0 +1,51 @@ +// 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.Collections.Concurrent; +using System.Threading; + +namespace Microsoft.JSInterop +{ + internal class DotNetObjectRefManager + { + private long _nextId = 0; // 0 signals no object, but we increment prior to assignment. The first tracked object should have id 1 + private readonly ConcurrentDictionary _trackedRefsById = new ConcurrentDictionary(); + + public static DotNetObjectRefManager Current + { + get + { + if (!(JSRuntime.Current is JSRuntimeBase jsRuntimeBase)) + { + throw new InvalidOperationException("JSRuntime must be set up correctly and must be an instance of JSRuntimeBase to use DotNetObjectRef."); + } + + return jsRuntimeBase.ObjectRefManager; + } + } + + public long TrackObject(IDotNetObjectRef dotNetObjectRef) + { + var dotNetObjectId = Interlocked.Increment(ref _nextId); + _trackedRefsById[dotNetObjectId] = dotNetObjectRef; + + return dotNetObjectId; + } + + public object FindDotNetObject(long dotNetObjectId) + { + return _trackedRefsById.TryGetValue(dotNetObjectId, out var dotNetObjectRef) + ? dotNetObjectRef.Value + : throw new ArgumentException($"There is no tracked object with id '{dotNetObjectId}'. Perhaps the DotNetObjectRef instance was already disposed.", nameof(dotNetObjectId)); + + } + + /// + /// Stops tracking the specified .NET object reference. + /// This may be invoked either by disposing a DotNetObjectRef in .NET code, or via JS interop by calling "dispose" on the corresponding instance in JavaScript code + /// + /// The ID of the . + public void ReleaseDotNetObject(long dotNetObjectId) => _trackedRefsById.TryRemove(dotNetObjectId, out _); + } +} diff --git a/src/JSInterop/Microsoft.JSInterop/src/DotNetObjectRefOfT.cs b/src/JSInterop/Microsoft.JSInterop/src/DotNetObjectRefOfT.cs new file mode 100644 index 0000000000..8b7035e957 --- /dev/null +++ b/src/JSInterop/Microsoft.JSInterop/src/DotNetObjectRefOfT.cs @@ -0,0 +1,76 @@ +// 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.ComponentModel; +using System.Text.Json.Serialization; + +namespace Microsoft.JSInterop +{ + /// + /// Wraps a JS interop argument, indicating that the value should not be serialized as JSON + /// but instead should be passed as a reference. + /// + /// To avoid leaking memory, the reference must later be disposed by JS code or by .NET code. + /// + /// The type of the value to wrap. + public sealed class DotNetObjectRef : IDotNetObjectRef, IDisposable where TValue : class + { + private long? _trackingId; + + /// + /// This API is for meant for JSON deserialization and should not be used by user code. + /// + [EditorBrowsable(EditorBrowsableState.Never)] + public DotNetObjectRef() + { + } + + /// + /// Initializes a new instance of . + /// + /// The value to pass by reference. + internal DotNetObjectRef(TValue value) + { + Value = value; + _trackingId = DotNetObjectRefManager.Current.TrackObject(this); + } + + /// + /// Gets the object instance represented by this wrapper. + /// + [JsonIgnore] + public TValue Value { get; private set; } + + /// + /// This API is for meant for JSON serialization and should not be used by user code. + /// + [EditorBrowsable(EditorBrowsableState.Never)] + public long __dotNetObject + { + get => _trackingId.Value; + set + { + if (_trackingId != null) + { + throw new InvalidOperationException($"{nameof(DotNetObjectRef)} cannot be reinitialized."); + } + + _trackingId = value; + Value = (TValue)DotNetObjectRefManager.Current.FindDotNetObject(value); + } + } + + object IDotNetObjectRef.Value => Value; + + /// + /// Stops tracking this object reference, allowing it to be garbage collected + /// (if there are no other references to it). Once the instance is disposed, it + /// can no longer be used in interop calls from JavaScript code. + /// + public void Dispose() + { + DotNetObjectRefManager.Current.ReleaseDotNetObject(_trackingId.Value); + } + } +} diff --git a/src/JSInterop/Microsoft.JSInterop/src/ICustomArgSerializer.cs b/src/JSInterop/Microsoft.JSInterop/src/ICustomArgSerializer.cs deleted file mode 100644 index f4012af8e9..0000000000 --- a/src/JSInterop/Microsoft.JSInterop/src/ICustomArgSerializer.cs +++ /dev/null @@ -1,22 +0,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. - -namespace Microsoft.JSInterop.Internal -{ - // This is "soft" internal because we're trying to avoid expanding JsonUtil into a sophisticated - // API. Developers who want that would be better served by using a different JSON package - // instead. Also the perf implications of the ICustomArgSerializer approach aren't ideal - // (it forces structs to be boxed, and returning a dictionary means lots more allocations - // and boxing of any value-typed properties). - - /// - /// Internal. Intended for framework use only. - /// - public interface ICustomArgSerializer - { - /// - /// Internal. Intended for framework use only. - /// - object ToJsonPrimitive(); - } -} diff --git a/src/JSInterop/Microsoft.JSInterop/src/IDotNetObjectRef.cs b/src/JSInterop/Microsoft.JSInterop/src/IDotNetObjectRef.cs new file mode 100644 index 0000000000..5f21808a9f --- /dev/null +++ b/src/JSInterop/Microsoft.JSInterop/src/IDotNetObjectRef.cs @@ -0,0 +1,12 @@ +// 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; + +namespace Microsoft.JSInterop +{ + internal interface IDotNetObjectRef : IDisposable + { + public object Value { get; } + } +} diff --git a/src/JSInterop/Microsoft.JSInterop/src/IJSRuntime.cs b/src/JSInterop/Microsoft.JSInterop/src/IJSRuntime.cs index b56d1f0089..97713bb3c1 100644 --- a/src/JSInterop/Microsoft.JSInterop/src/IJSRuntime.cs +++ b/src/JSInterop/Microsoft.JSInterop/src/IJSRuntime.cs @@ -1,6 +1,7 @@ // 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.Threading.Tasks; namespace Microsoft.JSInterop @@ -13,20 +14,10 @@ namespace Microsoft.JSInterop /// /// Invokes the specified JavaScript function asynchronously. /// - /// The JSON-serializable return type. + /// The JSON-serializable return type. /// An identifier for the function to invoke. For example, the value "someScope.someFunction" will invoke the function window.someScope.someFunction. /// JSON-serializable arguments. - /// An instance of obtained by JSON-deserializing the return value. - Task InvokeAsync(string identifier, params object[] args); - - /// - /// Stops tracking the .NET object represented by the . - /// This allows it to be garbage collected (if nothing else holds a reference to it) - /// and means the JS-side code can no longer invoke methods on the instance or pass - /// it as an argument to subsequent calls. - /// - /// The reference to stop tracking. - /// This method is called automatically by . - void UntrackObjectRef(DotNetObjectRef dotNetObjectRef); + /// An instance of obtained by JSON-deserializing the return value. + Task InvokeAsync(string identifier, params object[] args); } } diff --git a/src/JSInterop/Microsoft.JSInterop/src/InteropArgSerializerStrategy.cs b/src/JSInterop/Microsoft.JSInterop/src/InteropArgSerializerStrategy.cs deleted file mode 100644 index 663c1cf85a..0000000000 --- a/src/JSInterop/Microsoft.JSInterop/src/InteropArgSerializerStrategy.cs +++ /dev/null @@ -1,121 +0,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 Microsoft.JSInterop.Internal; -using SimpleJson; -using System; -using System.Collections.Generic; - -namespace Microsoft.JSInterop -{ - internal class InteropArgSerializerStrategy : PocoJsonSerializerStrategy - { - private readonly JSRuntimeBase _jsRuntime; - private const string _dotNetObjectPrefix = "__dotNetObject:"; - private object _storageLock = new object(); - private long _nextId = 1; // Start at 1, because 0 signals "no object" - private Dictionary _trackedRefsById = new Dictionary(); - private Dictionary _trackedIdsByRef = new Dictionary(); - - public InteropArgSerializerStrategy(JSRuntimeBase jsRuntime) - { - _jsRuntime = jsRuntime ?? throw new ArgumentNullException(nameof(jsRuntime)); - } - - protected override bool TrySerializeKnownTypes(object input, out object output) - { - switch (input) - { - case DotNetObjectRef marshalByRefValue: - EnsureDotNetObjectTracked(marshalByRefValue, out var id); - - // Special value format recognized by the code in Microsoft.JSInterop.js - // If we have to make it more clash-resistant, we can do - output = _dotNetObjectPrefix + id; - - return true; - - case ICustomArgSerializer customArgSerializer: - output = customArgSerializer.ToJsonPrimitive(); - return true; - - default: - return base.TrySerializeKnownTypes(input, out output); - } - } - - public override object DeserializeObject(object value, Type type) - { - if (value is string valueString) - { - if (valueString.StartsWith(_dotNetObjectPrefix)) - { - var dotNetObjectId = long.Parse(valueString.Substring(_dotNetObjectPrefix.Length)); - return FindDotNetObject(dotNetObjectId); - } - } - - return base.DeserializeObject(value, type); - } - - public object FindDotNetObject(long dotNetObjectId) - { - lock (_storageLock) - { - return _trackedRefsById.TryGetValue(dotNetObjectId, out var dotNetObjectRef) - ? dotNetObjectRef.Value - : throw new ArgumentException($"There is no tracked object with id '{dotNetObjectId}'. Perhaps the reference was already released.", nameof(dotNetObjectId)); - } - } - - /// - /// Stops tracking the specified .NET object reference. - /// This overload is typically invoked from JS code via JS interop. - /// - /// The ID of the . - public void ReleaseDotNetObject(long dotNetObjectId) - { - lock (_storageLock) - { - if (_trackedRefsById.TryGetValue(dotNetObjectId, out var dotNetObjectRef)) - { - _trackedRefsById.Remove(dotNetObjectId); - _trackedIdsByRef.Remove(dotNetObjectRef); - } - } - } - - /// - /// Stops tracking the specified .NET object reference. - /// This overload is typically invoked from .NET code by . - /// - /// The . - public void ReleaseDotNetObject(DotNetObjectRef dotNetObjectRef) - { - lock (_storageLock) - { - if (_trackedIdsByRef.TryGetValue(dotNetObjectRef, out var dotNetObjectId)) - { - _trackedRefsById.Remove(dotNetObjectId); - _trackedIdsByRef.Remove(dotNetObjectRef); - } - } - } - - private void EnsureDotNetObjectTracked(DotNetObjectRef dotNetObjectRef, out long dotNetObjectId) - { - dotNetObjectRef.EnsureAttachedToJsRuntime(_jsRuntime); - - lock (_storageLock) - { - // Assign an ID only if it doesn't already have one - if (!_trackedIdsByRef.TryGetValue(dotNetObjectRef, out dotNetObjectId)) - { - dotNetObjectId = _nextId++; - _trackedRefsById.Add(dotNetObjectId, dotNetObjectRef); - _trackedIdsByRef.Add(dotNetObjectRef, dotNetObjectId); - } - } - } - } -} diff --git a/src/JSInterop/Microsoft.JSInterop/src/JSAsyncCallResult.cs b/src/JSInterop/Microsoft.JSInterop/src/JSAsyncCallResult.cs index d46517eddc..1ea8c47995 100644 --- a/src/JSInterop/Microsoft.JSInterop/src/JSAsyncCallResult.cs +++ b/src/JSInterop/Microsoft.JSInterop/src/JSAsyncCallResult.cs @@ -1,6 +1,9 @@ // 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.ComponentModel; +using System.Text.Json; + namespace Microsoft.JSInterop.Internal { // This type takes care of a special case in handling the result of an async call from @@ -20,17 +23,16 @@ namespace Microsoft.JSInterop.Internal /// /// Intended for framework use only. /// - public class JSAsyncCallResult + [EditorBrowsable(EditorBrowsableState.Never)] + public sealed class JSAsyncCallResult { - internal object ResultOrException { get; } - - /// - /// Constructs an instance of . - /// - /// The result of the call. - internal JSAsyncCallResult(object resultOrException) + internal JSAsyncCallResult(JsonDocument document, JsonElement jsonElement) { - ResultOrException = resultOrException; + JsonDocument = document; + JsonElement = jsonElement; } + + internal JsonElement JsonElement { get; } + internal JsonDocument JsonDocument { get; } } } diff --git a/src/JSInterop/Microsoft.JSInterop/src/JSInProcessRuntimeBase.cs b/src/JSInterop/Microsoft.JSInterop/src/JSInProcessRuntimeBase.cs index 49a47d0595..7e6dcdd462 100644 --- a/src/JSInterop/Microsoft.JSInterop/src/JSInProcessRuntimeBase.cs +++ b/src/JSInterop/Microsoft.JSInterop/src/JSInProcessRuntimeBase.cs @@ -1,6 +1,8 @@ // 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.Text.Json.Serialization; + namespace Microsoft.JSInterop { /// @@ -11,14 +13,19 @@ namespace Microsoft.JSInterop /// /// Invokes the specified JavaScript function synchronously. /// - /// The JSON-serializable return type. + /// The JSON-serializable return type. /// An identifier for the function to invoke. For example, the value "someScope.someFunction" will invoke the function window.someScope.someFunction. /// JSON-serializable arguments. - /// An instance of obtained by JSON-deserializing the return value. - public T Invoke(string identifier, params object[] args) + /// An instance of obtained by JSON-deserializing the return value. + public TValue Invoke(string identifier, params object[] args) { - var resultJson = InvokeJS(identifier, Json.Serialize(args, ArgSerializerStrategy)); - return Json.Deserialize(resultJson, ArgSerializerStrategy); + var resultJson = InvokeJS(identifier, JsonSerializer.ToString(args, JsonSerializerOptionsProvider.Options)); + if (resultJson is null) + { + return default; + } + + return JsonSerializer.Parse(resultJson, JsonSerializerOptionsProvider.Options); } /// diff --git a/src/JSInterop/Microsoft.JSInterop/src/JSRuntimeBase.cs b/src/JSInterop/Microsoft.JSInterop/src/JSRuntimeBase.cs index d18bc7f4fe..70ab856a89 100644 --- a/src/JSInterop/Microsoft.JSInterop/src/JSRuntimeBase.cs +++ b/src/JSInterop/Microsoft.JSInterop/src/JSRuntimeBase.cs @@ -4,8 +4,10 @@ using System; using System.Collections.Concurrent; using System.Runtime.ExceptionServices; +using System.Text.Json.Serialization; using System.Threading; using System.Threading.Tasks; +using Microsoft.JSInterop.Internal; namespace Microsoft.JSInterop { @@ -18,19 +20,7 @@ namespace Microsoft.JSInterop private readonly ConcurrentDictionary _pendingTasks = new ConcurrentDictionary(); - internal InteropArgSerializerStrategy ArgSerializerStrategy { get; } - - /// - /// Constructs an instance of . - /// - public JSRuntimeBase() - { - ArgSerializerStrategy = new InteropArgSerializerStrategy(this); - } - - /// - public void UntrackObjectRef(DotNetObjectRef dotNetObjectRef) - => ArgSerializerStrategy.ReleaseDotNetObject(dotNetObjectRef); + internal DotNetObjectRefManager ObjectRefManager { get; } = new DotNetObjectRefManager(); /// /// Invokes the specified JavaScript function asynchronously. @@ -51,9 +41,9 @@ namespace Microsoft.JSInterop try { - var argsJson = args?.Length > 0 - ? Json.Serialize(args, ArgSerializerStrategy) - : null; + var argsJson = args?.Length > 0 ? + JsonSerializer.ToString(args, JsonSerializerOptionsProvider.Options) : + null; BeginInvokeJS(taskId, identifier, argsJson); return tcs.Task; } @@ -88,33 +78,32 @@ namespace Microsoft.JSInterop // We pass 0 as the async handle because we don't want the JS-side code to // send back any notification (we're just providing a result for an existing async call) - BeginInvokeJS(0, "DotNet.jsCallDispatcher.endInvokeDotNetFromJS", Json.Serialize(new[] { - callId, - success, - resultOrException - }, ArgSerializerStrategy)); + var args = JsonSerializer.ToString(new[] { callId, success, resultOrException }, JsonSerializerOptionsProvider.Options); + BeginInvokeJS(0, "DotNet.jsCallDispatcher.endInvokeDotNetFromJS", args); } - internal void EndInvokeJS(long asyncHandle, bool succeeded, object resultOrException) + internal void EndInvokeJS(long asyncHandle, bool succeeded, JSAsyncCallResult asyncCallResult) { - if (!_pendingTasks.TryRemove(asyncHandle, out var tcs)) + using (asyncCallResult?.JsonDocument) { - throw new ArgumentException($"There is no pending task with handle '{asyncHandle}'."); - } - - if (succeeded) - { - var resultType = TaskGenericsUtil.GetTaskCompletionSourceResultType(tcs); - if (resultOrException is SimpleJson.JsonObject || resultOrException is SimpleJson.JsonArray) + if (!_pendingTasks.TryRemove(asyncHandle, out var tcs)) { - resultOrException = ArgSerializerStrategy.DeserializeObject(resultOrException, resultType); + throw new ArgumentException($"There is no pending task with handle '{asyncHandle}'."); } - TaskGenericsUtil.SetTaskCompletionSourceResult(tcs, resultOrException); - } - else - { - TaskGenericsUtil.SetTaskCompletionSourceException(tcs, new JSException(resultOrException.ToString())); + if (succeeded) + { + var resultType = TaskGenericsUtil.GetTaskCompletionSourceResultType(tcs); + var result = asyncCallResult != null ? + JsonSerializer.Parse(asyncCallResult.JsonElement.GetRawText(), resultType, JsonSerializerOptionsProvider.Options) : + null; + TaskGenericsUtil.SetTaskCompletionSourceResult(tcs, result); + } + else + { + var exceptionText = asyncCallResult?.JsonElement.ToString() ?? string.Empty; + TaskGenericsUtil.SetTaskCompletionSourceException(tcs, new JSException(exceptionText)); + } } } } diff --git a/src/JSInterop/Microsoft.JSInterop/src/Json/CamelCase.cs b/src/JSInterop/Microsoft.JSInterop/src/Json/CamelCase.cs deleted file mode 100644 index 8caae1387b..0000000000 --- a/src/JSInterop/Microsoft.JSInterop/src/Json/CamelCase.cs +++ /dev/null @@ -1,59 +0,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; - -namespace Microsoft.JSInterop -{ - internal static class CamelCase - { - public static string MemberNameToCamelCase(string value) - { - if (string.IsNullOrEmpty(value)) - { - throw new ArgumentException( - $"The value '{value ?? "null"}' is not a valid member name.", - nameof(value)); - } - - // If we don't need to modify the value, bail out without creating a char array - if (!char.IsUpper(value[0])) - { - return value; - } - - // We have to modify at least one character - var chars = value.ToCharArray(); - - var length = chars.Length; - if (length < 2 || !char.IsUpper(chars[1])) - { - // Only the first character needs to be modified - // Note that this branch is functionally necessary, because the 'else' branch below - // never looks at char[1]. It's always looking at the n+2 character. - chars[0] = char.ToLowerInvariant(chars[0]); - } - else - { - // If chars[0] and chars[1] are both upper, then we'll lowercase the first char plus - // any consecutive uppercase ones, stopping if we find any char that is followed by a - // non-uppercase one - var i = 0; - while (i < length) - { - chars[i] = char.ToLowerInvariant(chars[i]); - - i++; - - // If the next-plus-one char isn't also uppercase, then we're now on the last uppercase, so stop - if (i < length - 1 && !char.IsUpper(chars[i + 1])) - { - break; - } - } - } - - return new string(chars); - } - } -} diff --git a/src/JSInterop/Microsoft.JSInterop/src/Json/Json.cs b/src/JSInterop/Microsoft.JSInterop/src/Json/Json.cs deleted file mode 100644 index 7275dfe427..0000000000 --- a/src/JSInterop/Microsoft.JSInterop/src/Json/Json.cs +++ /dev/null @@ -1,39 +0,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. - -namespace Microsoft.JSInterop -{ - /// - /// Provides mechanisms for converting between .NET objects and JSON strings for use - /// when making calls to JavaScript functions via . - /// - /// Warning: This is not intended as a general-purpose JSON library. It is only intended - /// for use when making calls via . Eventually its implementation - /// will be replaced by something more general-purpose. - /// - public static class Json - { - /// - /// Serializes the value as a JSON string. - /// - /// The value to serialize. - /// The JSON string. - public static string Serialize(object value) - => SimpleJson.SimpleJson.SerializeObject(value); - - internal static string Serialize(object value, SimpleJson.IJsonSerializerStrategy serializerStrategy) - => SimpleJson.SimpleJson.SerializeObject(value, serializerStrategy); - - /// - /// Deserializes the JSON string, creating an object of the specified generic type. - /// - /// The type of object to create. - /// The JSON string. - /// An object of the specified type. - public static T Deserialize(string json) - => SimpleJson.SimpleJson.DeserializeObject(json); - - internal static T Deserialize(string json, SimpleJson.IJsonSerializerStrategy serializerStrategy) - => SimpleJson.SimpleJson.DeserializeObject(json, serializerStrategy); - } -} diff --git a/src/JSInterop/Microsoft.JSInterop/src/Json/SimpleJson/README.txt b/src/JSInterop/Microsoft.JSInterop/src/Json/SimpleJson/README.txt deleted file mode 100644 index 5e58eb7106..0000000000 --- a/src/JSInterop/Microsoft.JSInterop/src/Json/SimpleJson/README.txt +++ /dev/null @@ -1,24 +0,0 @@ -SimpleJson is from https://github.com/facebook-csharp-sdk/simple-json - -LICENSE (from https://github.com/facebook-csharp-sdk/simple-json/blob/08b6871e8f63e866810d25e7a03c48502c9a234b/LICENSE.txt): -===== -Copyright (c) 2011, The Outercurve Foundation - -Permission is hereby granted, free of charge, to any person obtaining -a copy of this software and associated documentation files (the -"Software"), to deal in the Software without restriction, including -without limitation the rights to use, copy, modify, merge, publish, -distribute, sublicense, and/or sell copies of the Software, and to -permit persons to whom the Software is furnished to do so, subject to -the following conditions: - -The above copyright notice and this permission notice shall be -included in all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF -MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND -NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE -LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION -OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION -WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. \ No newline at end of file diff --git a/src/JSInterop/Microsoft.JSInterop/src/Json/SimpleJson/SimpleJson.cs b/src/JSInterop/Microsoft.JSInterop/src/Json/SimpleJson/SimpleJson.cs deleted file mode 100644 index d12c6fae30..0000000000 --- a/src/JSInterop/Microsoft.JSInterop/src/Json/SimpleJson/SimpleJson.cs +++ /dev/null @@ -1,2201 +0,0 @@ -//----------------------------------------------------------------------- -// -// Copyright (c) 2011, The Outercurve Foundation. -// -// Licensed under the MIT License (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// http://www.opensource.org/licenses/mit-license.php -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// -// Nathan Totten (ntotten.com), Jim Zimmerman (jimzimmerman.com) and Prabir Shrestha (prabir.me) -// https://github.com/facebook-csharp-sdk/simple-json -//----------------------------------------------------------------------- - -// VERSION: - -// NOTE: uncomment the following line to make SimpleJson class internal. -#define SIMPLE_JSON_INTERNAL - -// NOTE: uncomment the following line to make JsonArray and JsonObject class internal. -#define SIMPLE_JSON_OBJARRAYINTERNAL - -// NOTE: uncomment the following line to enable dynamic support. -//#define SIMPLE_JSON_DYNAMIC - -// NOTE: uncomment the following line to enable DataContract support. -//#define SIMPLE_JSON_DATACONTRACT - -// NOTE: uncomment the following line to enable IReadOnlyCollection and IReadOnlyList support. -//#define SIMPLE_JSON_READONLY_COLLECTIONS - -// NOTE: uncomment the following line to disable linq expressions/compiled lambda (better performance) instead of method.invoke(). -// define if you are using .net framework <= 3.0 or < WP7.5 -#define SIMPLE_JSON_NO_LINQ_EXPRESSION - -// NOTE: uncomment the following line if you are compiling under Window Metro style application/library. -// usually already defined in properties -//#define NETFX_CORE; - -// If you are targetting WinStore, WP8 and NET4.5+ PCL make sure to #define SIMPLE_JSON_TYPEINFO; - -// original json parsing code from http://techblog.procurios.nl/k/618/news/view/14605/14863/How-do-I-write-my-own-parser-for-JSON.html - -#if NETFX_CORE -#define SIMPLE_JSON_TYPEINFO -#endif - -using System; -using System.CodeDom.Compiler; -using System.Collections; -using System.Collections.Generic; -#if !SIMPLE_JSON_NO_LINQ_EXPRESSION -using System.Linq.Expressions; -#endif -using System.ComponentModel; -using System.Diagnostics.CodeAnalysis; -#if SIMPLE_JSON_DYNAMIC -using System.Dynamic; -#endif -using System.Globalization; -using System.Reflection; -using System.Runtime.Serialization; -using System.Text; -using Microsoft.JSInterop; -using SimpleJson.Reflection; - -// ReSharper disable LoopCanBeConvertedToQuery -// ReSharper disable RedundantExplicitArrayCreation -// ReSharper disable SuggestUseVarKeywordEvident -namespace SimpleJson -{ - /// - /// Represents the json array. - /// - [GeneratedCode("simple-json", "1.0.0")] - [EditorBrowsable(EditorBrowsableState.Never)] - [SuppressMessage("Microsoft.Naming", "CA1710:IdentifiersShouldHaveCorrectSuffix")] -#if SIMPLE_JSON_OBJARRAYINTERNAL - internal -#else - public -#endif - class JsonArray : List - { - /// - /// Initializes a new instance of the class. - /// - public JsonArray() { } - - /// - /// Initializes a new instance of the class. - /// - /// The capacity of the json array. - public JsonArray(int capacity) : base(capacity) { } - - /// - /// The json representation of the array. - /// - /// The json representation of the array. - public override string ToString() - { - return SimpleJson.SerializeObject(this) ?? string.Empty; - } - } - - /// - /// Represents the json object. - /// - [GeneratedCode("simple-json", "1.0.0")] - [EditorBrowsable(EditorBrowsableState.Never)] - [SuppressMessage("Microsoft.Naming", "CA1710:IdentifiersShouldHaveCorrectSuffix")] -#if SIMPLE_JSON_OBJARRAYINTERNAL - internal -#else - public -#endif - class JsonObject : -#if SIMPLE_JSON_DYNAMIC - DynamicObject, -#endif - IDictionary - { - /// - /// The internal member dictionary. - /// - private readonly Dictionary _members; - - /// - /// Initializes a new instance of . - /// - public JsonObject() - { - _members = new Dictionary(); - } - - /// - /// Initializes a new instance of . - /// - /// The implementation to use when comparing keys, or null to use the default for the type of the key. - public JsonObject(IEqualityComparer comparer) - { - _members = new Dictionary(comparer); - } - - /// - /// Gets the at the specified index. - /// - /// - public object this[int index] - { - get { return GetAtIndex(_members, index); } - } - - internal static object GetAtIndex(IDictionary obj, int index) - { - if (obj == null) - throw new ArgumentNullException("obj"); - if (index >= obj.Count) - throw new ArgumentOutOfRangeException("index"); - int i = 0; - foreach (KeyValuePair o in obj) - if (i++ == index) return o.Value; - return null; - } - - /// - /// Adds the specified key. - /// - /// The key. - /// The value. - public void Add(string key, object value) - { - _members.Add(key, value); - } - - /// - /// Determines whether the specified key contains key. - /// - /// The key. - /// - /// true if the specified key contains key; otherwise, false. - /// - public bool ContainsKey(string key) - { - return _members.ContainsKey(key); - } - - /// - /// Gets the keys. - /// - /// The keys. - public ICollection Keys - { - get { return _members.Keys; } - } - - /// - /// Removes the specified key. - /// - /// The key. - /// - public bool Remove(string key) - { - return _members.Remove(key); - } - - /// - /// Tries the get value. - /// - /// The key. - /// The value. - /// - public bool TryGetValue(string key, out object value) - { - return _members.TryGetValue(key, out value); - } - - /// - /// Gets the values. - /// - /// The values. - public ICollection Values - { - get { return _members.Values; } - } - - /// - /// Gets or sets the with the specified key. - /// - /// - public object this[string key] - { - get { return _members[key]; } - set { _members[key] = value; } - } - - /// - /// Adds the specified item. - /// - /// The item. - public void Add(KeyValuePair item) - { - _members.Add(item.Key, item.Value); - } - - /// - /// Clears this instance. - /// - public void Clear() - { - _members.Clear(); - } - - /// - /// Determines whether [contains] [the specified item]. - /// - /// The item. - /// - /// true if [contains] [the specified item]; otherwise, false. - /// - public bool Contains(KeyValuePair item) - { - return _members.ContainsKey(item.Key) && _members[item.Key] == item.Value; - } - - /// - /// Copies to. - /// - /// The array. - /// Index of the array. - public void CopyTo(KeyValuePair[] array, int arrayIndex) - { - if (array == null) throw new ArgumentNullException("array"); - int num = Count; - foreach (KeyValuePair kvp in this) - { - array[arrayIndex++] = kvp; - if (--num <= 0) - return; - } - } - - /// - /// Gets the count. - /// - /// The count. - public int Count - { - get { return _members.Count; } - } - - /// - /// Gets a value indicating whether this instance is read only. - /// - /// - /// true if this instance is read only; otherwise, false. - /// - public bool IsReadOnly - { - get { return false; } - } - - /// - /// Removes the specified item. - /// - /// The item. - /// - public bool Remove(KeyValuePair item) - { - return _members.Remove(item.Key); - } - - /// - /// Gets the enumerator. - /// - /// - public IEnumerator> GetEnumerator() - { - return _members.GetEnumerator(); - } - - /// - /// Returns an enumerator that iterates through a collection. - /// - /// - /// An object that can be used to iterate through the collection. - /// - IEnumerator IEnumerable.GetEnumerator() - { - return _members.GetEnumerator(); - } - - /// - /// Returns a json that represents the current . - /// - /// - /// A json that represents the current . - /// - public override string ToString() - { - return SimpleJson.SerializeObject(this); - } - -#if SIMPLE_JSON_DYNAMIC - /// - /// Provides implementation for type conversion operations. Classes derived from the class can override this method to specify dynamic behavior for operations that convert an object from one type to another. - /// - /// Provides information about the conversion operation. The binder.Type property provides the type to which the object must be converted. For example, for the statement (String)sampleObject in C# (CType(sampleObject, Type) in Visual Basic), where sampleObject is an instance of the class derived from the class, binder.Type returns the type. The binder.Explicit property provides information about the kind of conversion that occurs. It returns true for explicit conversion and false for implicit conversion. - /// The result of the type conversion operation. - /// - /// Alwasy returns true. - /// - public override bool TryConvert(ConvertBinder binder, out object result) - { - // - if (binder == null) - throw new ArgumentNullException("binder"); - // - Type targetType = binder.Type; - - if ((targetType == typeof(IEnumerable)) || - (targetType == typeof(IEnumerable>)) || - (targetType == typeof(IDictionary)) || - (targetType == typeof(IDictionary))) - { - result = this; - return true; - } - - return base.TryConvert(binder, out result); - } - - /// - /// Provides the implementation for operations that delete an object member. This method is not intended for use in C# or Visual Basic. - /// - /// Provides information about the deletion. - /// - /// Alwasy returns true. - /// - public override bool TryDeleteMember(DeleteMemberBinder binder) - { - // - if (binder == null) - throw new ArgumentNullException("binder"); - // - return _members.Remove(binder.Name); - } - - /// - /// Provides the implementation for operations that get a value by index. Classes derived from the class can override this method to specify dynamic behavior for indexing operations. - /// - /// Provides information about the operation. - /// The indexes that are used in the operation. For example, for the sampleObject[3] operation in C# (sampleObject(3) in Visual Basic), where sampleObject is derived from the DynamicObject class, is equal to 3. - /// The result of the index operation. - /// - /// Alwasy returns true. - /// - public override bool TryGetIndex(GetIndexBinder binder, object[] indexes, out object result) - { - if (indexes == null) throw new ArgumentNullException("indexes"); - if (indexes.Length == 1) - { - result = ((IDictionary)this)[(string)indexes[0]]; - return true; - } - result = null; - return true; - } - - /// - /// Provides the implementation for operations that get member values. Classes derived from the class can override this method to specify dynamic behavior for operations such as getting a value for a property. - /// - /// Provides information about the object that called the dynamic operation. The binder.Name property provides the name of the member on which the dynamic operation is performed. For example, for the Console.WriteLine(sampleObject.SampleProperty) statement, where sampleObject is an instance of the class derived from the class, binder.Name returns "SampleProperty". The binder.IgnoreCase property specifies whether the member name is case-sensitive. - /// The result of the get operation. For example, if the method is called for a property, you can assign the property value to . - /// - /// Alwasy returns true. - /// - public override bool TryGetMember(GetMemberBinder binder, out object result) - { - object value; - if (_members.TryGetValue(binder.Name, out value)) - { - result = value; - return true; - } - result = null; - return true; - } - - /// - /// Provides the implementation for operations that set a value by index. Classes derived from the class can override this method to specify dynamic behavior for operations that access objects by a specified index. - /// - /// Provides information about the operation. - /// The indexes that are used in the operation. For example, for the sampleObject[3] = 10 operation in C# (sampleObject(3) = 10 in Visual Basic), where sampleObject is derived from the class, is equal to 3. - /// The value to set to the object that has the specified index. For example, for the sampleObject[3] = 10 operation in C# (sampleObject(3) = 10 in Visual Basic), where sampleObject is derived from the class, is equal to 10. - /// - /// true if the operation is successful; otherwise, false. If this method returns false, the run-time binder of the language determines the behavior. (In most cases, a language-specific run-time exception is thrown. - /// - public override bool TrySetIndex(SetIndexBinder binder, object[] indexes, object value) - { - if (indexes == null) throw new ArgumentNullException("indexes"); - if (indexes.Length == 1) - { - ((IDictionary)this)[(string)indexes[0]] = value; - return true; - } - return base.TrySetIndex(binder, indexes, value); - } - - /// - /// Provides the implementation for operations that set member values. Classes derived from the class can override this method to specify dynamic behavior for operations such as setting a value for a property. - /// - /// Provides information about the object that called the dynamic operation. The binder.Name property provides the name of the member to which the value is being assigned. For example, for the statement sampleObject.SampleProperty = "Test", where sampleObject is an instance of the class derived from the class, binder.Name returns "SampleProperty". The binder.IgnoreCase property specifies whether the member name is case-sensitive. - /// The value to set to the member. For example, for sampleObject.SampleProperty = "Test", where sampleObject is an instance of the class derived from the class, the is "Test". - /// - /// true if the operation is successful; otherwise, false. If this method returns false, the run-time binder of the language determines the behavior. (In most cases, a language-specific run-time exception is thrown.) - /// - public override bool TrySetMember(SetMemberBinder binder, object value) - { - // - if (binder == null) - throw new ArgumentNullException("binder"); - // - _members[binder.Name] = value; - return true; - } - - /// - /// Returns the enumeration of all dynamic member names. - /// - /// - /// A sequence that contains dynamic member names. - /// - public override IEnumerable GetDynamicMemberNames() - { - foreach (var key in Keys) - yield return key; - } -#endif - } -} - -namespace SimpleJson -{ - /// - /// This class encodes and decodes JSON strings. - /// Spec. details, see http://www.json.org/ - /// - /// JSON uses Arrays and Objects. These correspond here to the datatypes JsonArray(IList<object>) and JsonObject(IDictionary<string,object>). - /// All numbers are parsed to doubles. - /// - [GeneratedCode("simple-json", "1.0.0")] -#if SIMPLE_JSON_INTERNAL - internal -#else - public -#endif - static class SimpleJson - { - private const int TOKEN_NONE = 0; - private const int TOKEN_CURLY_OPEN = 1; - private const int TOKEN_CURLY_CLOSE = 2; - private const int TOKEN_SQUARED_OPEN = 3; - private const int TOKEN_SQUARED_CLOSE = 4; - private const int TOKEN_COLON = 5; - private const int TOKEN_COMMA = 6; - private const int TOKEN_STRING = 7; - private const int TOKEN_NUMBER = 8; - private const int TOKEN_TRUE = 9; - private const int TOKEN_FALSE = 10; - private const int TOKEN_NULL = 11; - private const int BUILDER_CAPACITY = 2000; - - private static readonly char[] EscapeTable; - private static readonly char[] EscapeCharacters = new char[] { '"', '\\', '\b', '\f', '\n', '\r', '\t' }; - private static readonly string EscapeCharactersString = new string(EscapeCharacters); - - static SimpleJson() - { - EscapeTable = new char[93]; - EscapeTable['"'] = '"'; - EscapeTable['\\'] = '\\'; - EscapeTable['\b'] = 'b'; - EscapeTable['\f'] = 'f'; - EscapeTable['\n'] = 'n'; - EscapeTable['\r'] = 'r'; - EscapeTable['\t'] = 't'; - } - - /// - /// Parses the string json into a value - /// - /// A JSON string. - /// An IList<object>, a IDictionary<string,object>, a double, a string, null, true, or false - public static object DeserializeObject(string json) - { - object obj; - if (TryDeserializeObject(json, out obj)) - return obj; - throw new SerializationException("Invalid JSON string"); - } - - /// - /// Try parsing the json string into a value. - /// - /// - /// A JSON string. - /// - /// - /// The object. - /// - /// - /// Returns true if successful otherwise false. - /// - [SuppressMessage("Microsoft.Design", "CA1007:UseGenericsWhereAppropriate", Justification="Need to support .NET 2")] - public static bool TryDeserializeObject(string json, out object obj) - { - bool success = true; - if (json != null) - { - char[] charArray = json.ToCharArray(); - int index = 0; - obj = ParseValue(charArray, ref index, ref success); - } - else - obj = null; - - return success; - } - - public static object DeserializeObject(string json, Type type, IJsonSerializerStrategy jsonSerializerStrategy) - { - object jsonObject = DeserializeObject(json); - return type == null || jsonObject != null && ReflectionUtils.IsAssignableFrom(jsonObject.GetType(), type) - ? jsonObject - : (jsonSerializerStrategy ?? CurrentJsonSerializerStrategy).DeserializeObject(jsonObject, type); - } - - public static object DeserializeObject(string json, Type type) - { - return DeserializeObject(json, type, null); - } - - public static T DeserializeObject(string json, IJsonSerializerStrategy jsonSerializerStrategy) - { - return (T)DeserializeObject(json, typeof(T), jsonSerializerStrategy); - } - - public static T DeserializeObject(string json) - { - return (T)DeserializeObject(json, typeof(T), null); - } - - /// - /// Converts a IDictionary<string,object> / IList<object> object into a JSON string - /// - /// A IDictionary<string,object> / IList<object> - /// Serializer strategy to use - /// A JSON encoded string, or null if object 'json' is not serializable - public static string SerializeObject(object json, IJsonSerializerStrategy jsonSerializerStrategy) - { - StringBuilder builder = new StringBuilder(BUILDER_CAPACITY); - bool success = SerializeValue(jsonSerializerStrategy, json, builder); - return (success ? builder.ToString() : null); - } - - public static string SerializeObject(object json) - { - return SerializeObject(json, CurrentJsonSerializerStrategy); - } - - public static string EscapeToJavascriptString(string jsonString) - { - if (string.IsNullOrEmpty(jsonString)) - return jsonString; - - StringBuilder sb = new StringBuilder(); - char c; - - for (int i = 0; i < jsonString.Length; ) - { - c = jsonString[i++]; - - if (c == '\\') - { - int remainingLength = jsonString.Length - i; - if (remainingLength >= 2) - { - char lookahead = jsonString[i]; - if (lookahead == '\\') - { - sb.Append('\\'); - ++i; - } - else if (lookahead == '"') - { - sb.Append("\""); - ++i; - } - else if (lookahead == 't') - { - sb.Append('\t'); - ++i; - } - else if (lookahead == 'b') - { - sb.Append('\b'); - ++i; - } - else if (lookahead == 'n') - { - sb.Append('\n'); - ++i; - } - else if (lookahead == 'r') - { - sb.Append('\r'); - ++i; - } - } - } - else - { - sb.Append(c); - } - } - return sb.ToString(); - } - - static IDictionary ParseObject(char[] json, ref int index, ref bool success) - { - IDictionary table = new JsonObject(); - int token; - - // { - NextToken(json, ref index); - - bool done = false; - while (!done) - { - token = LookAhead(json, index); - if (token == TOKEN_NONE) - { - success = false; - return null; - } - else if (token == TOKEN_COMMA) - NextToken(json, ref index); - else if (token == TOKEN_CURLY_CLOSE) - { - NextToken(json, ref index); - return table; - } - else - { - // name - string name = ParseString(json, ref index, ref success); - if (!success) - { - success = false; - return null; - } - // : - token = NextToken(json, ref index); - if (token != TOKEN_COLON) - { - success = false; - return null; - } - // value - object value = ParseValue(json, ref index, ref success); - if (!success) - { - success = false; - return null; - } - table[name] = value; - } - } - return table; - } - - static JsonArray ParseArray(char[] json, ref int index, ref bool success) - { - JsonArray array = new JsonArray(); - - // [ - NextToken(json, ref index); - - bool done = false; - while (!done) - { - int token = LookAhead(json, index); - if (token == TOKEN_NONE) - { - success = false; - return null; - } - else if (token == TOKEN_COMMA) - NextToken(json, ref index); - else if (token == TOKEN_SQUARED_CLOSE) - { - NextToken(json, ref index); - break; - } - else - { - object value = ParseValue(json, ref index, ref success); - if (!success) - return null; - array.Add(value); - } - } - return array; - } - - static object ParseValue(char[] json, ref int index, ref bool success) - { - switch (LookAhead(json, index)) - { - case TOKEN_STRING: - return ParseString(json, ref index, ref success); - case TOKEN_NUMBER: - return ParseNumber(json, ref index, ref success); - case TOKEN_CURLY_OPEN: - return ParseObject(json, ref index, ref success); - case TOKEN_SQUARED_OPEN: - return ParseArray(json, ref index, ref success); - case TOKEN_TRUE: - NextToken(json, ref index); - return true; - case TOKEN_FALSE: - NextToken(json, ref index); - return false; - case TOKEN_NULL: - NextToken(json, ref index); - return null; - case TOKEN_NONE: - break; - } - success = false; - return null; - } - - static string ParseString(char[] json, ref int index, ref bool success) - { - StringBuilder s = new StringBuilder(BUILDER_CAPACITY); - char c; - - EatWhitespace(json, ref index); - - // " - c = json[index++]; - bool complete = false; - while (!complete) - { - if (index == json.Length) - break; - - c = json[index++]; - if (c == '"') - { - complete = true; - break; - } - else if (c == '\\') - { - if (index == json.Length) - break; - c = json[index++]; - if (c == '"') - s.Append('"'); - else if (c == '\\') - s.Append('\\'); - else if (c == '/') - s.Append('/'); - else if (c == 'b') - s.Append('\b'); - else if (c == 'f') - s.Append('\f'); - else if (c == 'n') - s.Append('\n'); - else if (c == 'r') - s.Append('\r'); - else if (c == 't') - s.Append('\t'); - else if (c == 'u') - { - int remainingLength = json.Length - index; - if (remainingLength >= 4) - { - // parse the 32 bit hex into an integer codepoint - uint codePoint; - if (!(success = UInt32.TryParse(new string(json, index, 4), NumberStyles.HexNumber, CultureInfo.InvariantCulture, out codePoint))) - return ""; - - // convert the integer codepoint to a unicode char and add to string - if (0xD800 <= codePoint && codePoint <= 0xDBFF) // if high surrogate - { - index += 4; // skip 4 chars - remainingLength = json.Length - index; - if (remainingLength >= 6) - { - uint lowCodePoint; - if (new string(json, index, 2) == "\\u" && UInt32.TryParse(new string(json, index + 2, 4), NumberStyles.HexNumber, CultureInfo.InvariantCulture, out lowCodePoint)) - { - if (0xDC00 <= lowCodePoint && lowCodePoint <= 0xDFFF) // if low surrogate - { - s.Append((char)codePoint); - s.Append((char)lowCodePoint); - index += 6; // skip 6 chars - continue; - } - } - } - success = false; // invalid surrogate pair - return ""; - } - s.Append(ConvertFromUtf32((int)codePoint)); - // skip 4 chars - index += 4; - } - else - break; - } - } - else - s.Append(c); - } - if (!complete) - { - success = false; - return null; - } - return s.ToString(); - } - - private static string ConvertFromUtf32(int utf32) - { - // http://www.java2s.com/Open-Source/CSharp/2.6.4-mono-.net-core/System/System/Char.cs.htm - if (utf32 < 0 || utf32 > 0x10FFFF) - throw new ArgumentOutOfRangeException("utf32", "The argument must be from 0 to 0x10FFFF."); - if (0xD800 <= utf32 && utf32 <= 0xDFFF) - throw new ArgumentOutOfRangeException("utf32", "The argument must not be in surrogate pair range."); - if (utf32 < 0x10000) - return new string((char)utf32, 1); - utf32 -= 0x10000; - return new string(new char[] { (char)((utf32 >> 10) + 0xD800), (char)(utf32 % 0x0400 + 0xDC00) }); - } - - static object ParseNumber(char[] json, ref int index, ref bool success) - { - EatWhitespace(json, ref index); - int lastIndex = GetLastIndexOfNumber(json, index); - int charLength = (lastIndex - index) + 1; - object returnNumber; - string str = new string(json, index, charLength); - if (str.IndexOf(".", StringComparison.OrdinalIgnoreCase) != -1 || str.IndexOf("e", StringComparison.OrdinalIgnoreCase) != -1) - { - double number; - success = double.TryParse(new string(json, index, charLength), NumberStyles.Any, CultureInfo.InvariantCulture, out number); - returnNumber = number; - } - else - { - long number; - success = long.TryParse(new string(json, index, charLength), NumberStyles.Any, CultureInfo.InvariantCulture, out number); - returnNumber = number; - } - index = lastIndex + 1; - return returnNumber; - } - - static int GetLastIndexOfNumber(char[] json, int index) - { - int lastIndex; - for (lastIndex = index; lastIndex < json.Length; lastIndex++) - if ("0123456789+-.eE".IndexOf(json[lastIndex]) == -1) break; - return lastIndex - 1; - } - - static void EatWhitespace(char[] json, ref int index) - { - for (; index < json.Length; index++) { - switch (json[index]) { - case ' ': - case '\t': - case '\n': - case '\r': - case '\b': - case '\f': - break; - default: - return; - } - } - } - - static int LookAhead(char[] json, int index) - { - int saveIndex = index; - return NextToken(json, ref saveIndex); - } - - [SuppressMessage("Microsoft.Maintainability", "CA1502:AvoidExcessiveComplexity")] - static int NextToken(char[] json, ref int index) - { - EatWhitespace(json, ref index); - if (index == json.Length) - return TOKEN_NONE; - char c = json[index]; - index++; - switch (c) - { - case '{': - return TOKEN_CURLY_OPEN; - case '}': - return TOKEN_CURLY_CLOSE; - case '[': - return TOKEN_SQUARED_OPEN; - case ']': - return TOKEN_SQUARED_CLOSE; - case ',': - return TOKEN_COMMA; - case '"': - return TOKEN_STRING; - case '0': - case '1': - case '2': - case '3': - case '4': - case '5': - case '6': - case '7': - case '8': - case '9': - case '-': - return TOKEN_NUMBER; - case ':': - return TOKEN_COLON; - } - index--; - int remainingLength = json.Length - index; - // false - if (remainingLength >= 5) - { - if (json[index] == 'f' && json[index + 1] == 'a' && json[index + 2] == 'l' && json[index + 3] == 's' && json[index + 4] == 'e') - { - index += 5; - return TOKEN_FALSE; - } - } - // true - if (remainingLength >= 4) - { - if (json[index] == 't' && json[index + 1] == 'r' && json[index + 2] == 'u' && json[index + 3] == 'e') - { - index += 4; - return TOKEN_TRUE; - } - } - // null - if (remainingLength >= 4) - { - if (json[index] == 'n' && json[index + 1] == 'u' && json[index + 2] == 'l' && json[index + 3] == 'l') - { - index += 4; - return TOKEN_NULL; - } - } - return TOKEN_NONE; - } - - static bool SerializeValue(IJsonSerializerStrategy jsonSerializerStrategy, object value, StringBuilder builder) - { - bool success = true; - string stringValue = value as string; - if (stringValue != null) - success = SerializeString(stringValue, builder); - else - { - IDictionary dict = value as IDictionary; - if (dict != null) - { - success = SerializeObject(jsonSerializerStrategy, dict.Keys, dict.Values, builder); - } - else - { - IDictionary stringDictionary = value as IDictionary; - if (stringDictionary != null) - { - success = SerializeObject(jsonSerializerStrategy, stringDictionary.Keys, stringDictionary.Values, builder); - } - else - { - IEnumerable enumerableValue = value as IEnumerable; - if (enumerableValue != null) - success = SerializeArray(jsonSerializerStrategy, enumerableValue, builder); - else if (IsNumeric(value)) - success = SerializeNumber(value, builder); - else if (value is bool) - builder.Append((bool)value ? "true" : "false"); - else if (value == null) - builder.Append("null"); - else - { - object serializedObject; - success = jsonSerializerStrategy.TrySerializeNonPrimitiveObject(value, out serializedObject); - if (success) - SerializeValue(jsonSerializerStrategy, serializedObject, builder); - } - } - } - } - return success; - } - - static bool SerializeObject(IJsonSerializerStrategy jsonSerializerStrategy, IEnumerable keys, IEnumerable values, StringBuilder builder) - { - builder.Append("{"); - IEnumerator ke = keys.GetEnumerator(); - IEnumerator ve = values.GetEnumerator(); - bool first = true; - while (ke.MoveNext() && ve.MoveNext()) - { - object key = ke.Current; - object value = ve.Current; - if (!first) - builder.Append(","); - string stringKey = key as string; - if (stringKey != null) - SerializeString(stringKey, builder); - else - if (!SerializeValue(jsonSerializerStrategy, value, builder)) return false; - builder.Append(":"); - if (!SerializeValue(jsonSerializerStrategy, value, builder)) - return false; - first = false; - } - builder.Append("}"); - return true; - } - - static bool SerializeArray(IJsonSerializerStrategy jsonSerializerStrategy, IEnumerable anArray, StringBuilder builder) - { - builder.Append("["); - bool first = true; - foreach (object value in anArray) - { - if (!first) - builder.Append(","); - if (!SerializeValue(jsonSerializerStrategy, value, builder)) - return false; - first = false; - } - builder.Append("]"); - return true; - } - - static bool SerializeString(string aString, StringBuilder builder) - { - // Happy path if there's nothing to be escaped. IndexOfAny is highly optimized (and unmanaged) - if (aString.IndexOfAny(EscapeCharacters) == -1) - { - builder.Append('"'); - builder.Append(aString); - builder.Append('"'); - - return true; - } - - builder.Append('"'); - int safeCharacterCount = 0; - char[] charArray = aString.ToCharArray(); - - for (int i = 0; i < charArray.Length; i++) - { - char c = charArray[i]; - - // Non ascii characters are fine, buffer them up and send them to the builder - // in larger chunks if possible. The escape table is a 1:1 translation table - // with \0 [default(char)] denoting a safe character. - if (c >= EscapeTable.Length || EscapeTable[c] == default(char)) - { - safeCharacterCount++; - } - else - { - if (safeCharacterCount > 0) - { - builder.Append(charArray, i - safeCharacterCount, safeCharacterCount); - safeCharacterCount = 0; - } - - builder.Append('\\'); - builder.Append(EscapeTable[c]); - } - } - - if (safeCharacterCount > 0) - { - builder.Append(charArray, charArray.Length - safeCharacterCount, safeCharacterCount); - } - - builder.Append('"'); - return true; - } - - static bool SerializeNumber(object number, StringBuilder builder) - { - if (number is long) - builder.Append(((long)number).ToString(CultureInfo.InvariantCulture)); - else if (number is ulong) - builder.Append(((ulong)number).ToString(CultureInfo.InvariantCulture)); - else if (number is int) - builder.Append(((int)number).ToString(CultureInfo.InvariantCulture)); - else if (number is uint) - builder.Append(((uint)number).ToString(CultureInfo.InvariantCulture)); - else if (number is decimal) - builder.Append(((decimal)number).ToString(CultureInfo.InvariantCulture)); - else if (number is float) - builder.Append(((float)number).ToString(CultureInfo.InvariantCulture)); - else - builder.Append(Convert.ToDouble(number, CultureInfo.InvariantCulture).ToString("r", CultureInfo.InvariantCulture)); - return true; - } - - /// - /// Determines if a given object is numeric in any way - /// (can be integer, double, null, etc). - /// - static bool IsNumeric(object value) - { - if (value is sbyte) return true; - if (value is byte) return true; - if (value is short) return true; - if (value is ushort) return true; - if (value is int) return true; - if (value is uint) return true; - if (value is long) return true; - if (value is ulong) return true; - if (value is float) return true; - if (value is double) return true; - if (value is decimal) return true; - return false; - } - - private static IJsonSerializerStrategy _currentJsonSerializerStrategy; - public static IJsonSerializerStrategy CurrentJsonSerializerStrategy - { - get - { - return _currentJsonSerializerStrategy ?? - (_currentJsonSerializerStrategy = -#if SIMPLE_JSON_DATACONTRACT - DataContractJsonSerializerStrategy -#else - PocoJsonSerializerStrategy -#endif -); - } - set - { - _currentJsonSerializerStrategy = value; - } - } - - private static PocoJsonSerializerStrategy _pocoJsonSerializerStrategy; - [EditorBrowsable(EditorBrowsableState.Advanced)] - public static PocoJsonSerializerStrategy PocoJsonSerializerStrategy - { - get - { - return _pocoJsonSerializerStrategy ?? (_pocoJsonSerializerStrategy = new PocoJsonSerializerStrategy()); - } - } - -#if SIMPLE_JSON_DATACONTRACT - - private static DataContractJsonSerializerStrategy _dataContractJsonSerializerStrategy; - [System.ComponentModel.EditorBrowsable(EditorBrowsableState.Advanced)] - public static DataContractJsonSerializerStrategy DataContractJsonSerializerStrategy - { - get - { - return _dataContractJsonSerializerStrategy ?? (_dataContractJsonSerializerStrategy = new DataContractJsonSerializerStrategy()); - } - } - -#endif - } - - [GeneratedCode("simple-json", "1.0.0")] -#if SIMPLE_JSON_INTERNAL - internal -#else - public -#endif - interface IJsonSerializerStrategy - { - [SuppressMessage("Microsoft.Design", "CA1007:UseGenericsWhereAppropriate", Justification="Need to support .NET 2")] - bool TrySerializeNonPrimitiveObject(object input, out object output); - object DeserializeObject(object value, Type type); - } - - [GeneratedCode("simple-json", "1.0.0")] -#if SIMPLE_JSON_INTERNAL - internal -#else - public -#endif - class PocoJsonSerializerStrategy : IJsonSerializerStrategy - { - internal IDictionary ConstructorCache; - internal IDictionary> GetCache; - internal IDictionary>> SetCache; - - internal static readonly Type[] EmptyTypes = new Type[0]; - internal static readonly Type[] ArrayConstructorParameterTypes = new Type[] { typeof(int) }; - - private static readonly string[] Iso8601Format = new string[] - { - @"yyyy-MM-dd\THH:mm:ss.FFFFFFF\Z", - @"yyyy-MM-dd\THH:mm:ss\Z", - @"yyyy-MM-dd\THH:mm:ssK" - }; - - public PocoJsonSerializerStrategy() - { - ConstructorCache = new ReflectionUtils.ThreadSafeDictionary(ConstructorDelegateFactory); - GetCache = new ReflectionUtils.ThreadSafeDictionary>(GetterValueFactory); - SetCache = new ReflectionUtils.ThreadSafeDictionary>>(SetterValueFactory); - } - - protected virtual string MapClrMemberNameToJsonFieldName(string clrPropertyName) - { - return CamelCase.MemberNameToCamelCase(clrPropertyName); - } - - internal virtual ReflectionUtils.ConstructorDelegate ConstructorDelegateFactory(Type key) - { - // We need List(int) constructor so that DeserializeObject method will work for generating IList-declared values - var needsCapacityArgument = key.IsArray || key.IsConstructedGenericType && key.GetGenericTypeDefinition() == typeof(List<>); - return ReflectionUtils.GetConstructor(key, needsCapacityArgument ? ArrayConstructorParameterTypes : EmptyTypes); - } - - internal virtual IDictionary GetterValueFactory(Type type) - { - IDictionary result = new Dictionary(); - foreach (PropertyInfo propertyInfo in ReflectionUtils.GetProperties(type)) - { - if (propertyInfo.CanRead) - { - MethodInfo getMethod = ReflectionUtils.GetGetterMethodInfo(propertyInfo); - if (getMethod.IsStatic || !getMethod.IsPublic) - continue; - result[MapClrMemberNameToJsonFieldName(propertyInfo.Name)] = ReflectionUtils.GetGetMethod(propertyInfo); - } - } - foreach (FieldInfo fieldInfo in ReflectionUtils.GetFields(type)) - { - if (fieldInfo.IsStatic || !fieldInfo.IsPublic) - continue; - result[MapClrMemberNameToJsonFieldName(fieldInfo.Name)] = ReflectionUtils.GetGetMethod(fieldInfo); - } - return result; - } - - internal virtual IDictionary> SetterValueFactory(Type type) - { - // BLAZOR-SPECIFIC MODIFICATION FROM STOCK SIMPLEJSON: - // - // For incoming keys we match case-insensitively. But if two .NET properties differ only by case, - // it's ambiguous which should be used: the one that matches the incoming JSON exactly, or the - // one that uses 'correct' PascalCase corresponding to the incoming camelCase? What if neither - // meets these descriptions? - // - // To resolve this: - // - If multiple public properties differ only by case, we throw - // - If multiple public fields differ only by case, we throw - // - If there's a public property and a public field that differ only by case, we prefer the property - // This unambiguously selects one member, and that's what we'll use. - - IDictionary> result = new Dictionary>(StringComparer.OrdinalIgnoreCase); - foreach (PropertyInfo propertyInfo in ReflectionUtils.GetProperties(type)) - { - if (propertyInfo.CanWrite) - { - MethodInfo setMethod = ReflectionUtils.GetSetterMethodInfo(propertyInfo); - if (setMethod.IsStatic) - continue; - if (result.ContainsKey(propertyInfo.Name)) - { - throw new InvalidOperationException($"The type '{type.FullName}' contains multiple public properties with names case-insensitively matching '{propertyInfo.Name.ToLowerInvariant()}'. Such types cannot be used for JSON deserialization."); - } - result[propertyInfo.Name] = new KeyValuePair(propertyInfo.PropertyType, ReflectionUtils.GetSetMethod(propertyInfo)); - } - } - - IDictionary> fieldResult = new Dictionary>(StringComparer.OrdinalIgnoreCase); - foreach (FieldInfo fieldInfo in ReflectionUtils.GetFields(type)) - { - if (fieldInfo.IsInitOnly || fieldInfo.IsStatic || !fieldInfo.IsPublic) - continue; - if (fieldResult.ContainsKey(fieldInfo.Name)) - { - throw new InvalidOperationException($"The type '{type.FullName}' contains multiple public fields with names case-insensitively matching '{fieldInfo.Name.ToLowerInvariant()}'. Such types cannot be used for JSON deserialization."); - } - fieldResult[fieldInfo.Name] = new KeyValuePair(fieldInfo.FieldType, ReflectionUtils.GetSetMethod(fieldInfo)); - if (!result.ContainsKey(fieldInfo.Name)) - { - result[fieldInfo.Name] = fieldResult[fieldInfo.Name]; - } - } - - return result; - } - - public virtual bool TrySerializeNonPrimitiveObject(object input, out object output) - { - return TrySerializeKnownTypes(input, out output) || TrySerializeUnknownTypes(input, out output); - } - - [SuppressMessage("Microsoft.Maintainability", "CA1502:AvoidExcessiveComplexity")] - public virtual object DeserializeObject(object value, Type type) - { - if (type == null) throw new ArgumentNullException("type"); - string str = value as string; - - if (type == typeof (Guid) && string.IsNullOrEmpty(str)) - return default(Guid); - - if (type.IsEnum) - { - type = type.GetEnumUnderlyingType(); - } - - if (value == null) - return null; - - object obj = null; - - if (str != null) - { - if (str.Length != 0) // We know it can't be null now. - { - if (type == typeof(TimeSpan) || (ReflectionUtils.IsNullableType(type) && Nullable.GetUnderlyingType(type) == typeof(TimeSpan))) - return TimeSpan.ParseExact(str, "c", CultureInfo.InvariantCulture); - if (type == typeof(DateTime) || (ReflectionUtils.IsNullableType(type) && Nullable.GetUnderlyingType(type) == typeof(DateTime))) - return DateTime.TryParseExact(str, Iso8601Format, CultureInfo.InvariantCulture, DateTimeStyles.AssumeUniversal | DateTimeStyles.AdjustToUniversal, out var result) - ? result : DateTime.Parse(str, CultureInfo.InvariantCulture, DateTimeStyles.RoundtripKind); - if (type == typeof(DateTimeOffset) || (ReflectionUtils.IsNullableType(type) && Nullable.GetUnderlyingType(type) == typeof(DateTimeOffset))) - return DateTimeOffset.TryParseExact(str, Iso8601Format, CultureInfo.InvariantCulture, DateTimeStyles.AssumeUniversal | DateTimeStyles.AdjustToUniversal, out var result) - ? result : DateTimeOffset.Parse(str, CultureInfo.InvariantCulture, DateTimeStyles.RoundtripKind); - if (type == typeof(Guid) || (ReflectionUtils.IsNullableType(type) && Nullable.GetUnderlyingType(type) == typeof(Guid))) - return new Guid(str); - if (type == typeof(Uri)) - { - bool isValid = Uri.IsWellFormedUriString(str, UriKind.RelativeOrAbsolute); - - Uri result; - if (isValid && Uri.TryCreate(str, UriKind.RelativeOrAbsolute, out result)) - return result; - - return null; - } - - if (type == typeof(string)) - return str; - - return Convert.ChangeType(str, type, CultureInfo.InvariantCulture); - } - else - { - if (type == typeof(Guid)) - obj = default(Guid); - else if (ReflectionUtils.IsNullableType(type) && Nullable.GetUnderlyingType(type) == typeof(Guid)) - obj = null; - else - obj = str; - } - // Empty string case - if (!ReflectionUtils.IsNullableType(type) && Nullable.GetUnderlyingType(type) == typeof(Guid)) - return str; - } - else if (value is bool) - return value; - - bool valueIsLong = value is long; - bool valueIsDouble = value is double; - if ((valueIsLong && type == typeof(long)) || (valueIsDouble && type == typeof(double))) - return value; - if ((valueIsDouble && type != typeof(double)) || (valueIsLong && type != typeof(long))) - { - obj = type == typeof(int) || type == typeof(long) || type == typeof(double) || type == typeof(float) || type == typeof(bool) || type == typeof(decimal) || type == typeof(byte) || type == typeof(short) - ? Convert.ChangeType(value, type, CultureInfo.InvariantCulture) - : value; - } - else - { - IDictionary objects = value as IDictionary; - if (objects != null) - { - IDictionary jsonObject = objects; - - if (ReflectionUtils.IsTypeDictionary(type)) - { - // if dictionary then - Type[] types = ReflectionUtils.GetGenericTypeArguments(type); - Type keyType = types[0]; - Type valueType = types[1]; - - Type genericType = typeof(Dictionary<,>).MakeGenericType(keyType, valueType); - - IDictionary dict = (IDictionary)ConstructorCache[genericType](); - - foreach (KeyValuePair kvp in jsonObject) - dict.Add(kvp.Key, DeserializeObject(kvp.Value, valueType)); - - obj = dict; - } - else - { - if (type == typeof(object)) - obj = value; - else - { - var constructorDelegate = ConstructorCache[type] - ?? throw new InvalidOperationException($"Cannot deserialize JSON into type '{type.FullName}' because it does not have a public parameterless constructor."); - obj = constructorDelegate(); - - var setterCache = SetCache[type]; - foreach (var jsonKeyValuePair in jsonObject) - { - if (setterCache.TryGetValue(jsonKeyValuePair.Key, out var setter)) - { - var jsonValue = DeserializeObject(jsonKeyValuePair.Value, setter.Key); - setter.Value(obj, jsonValue); - } - } - } - } - } - else - { - IList valueAsList = value as IList; - if (valueAsList != null) - { - IList jsonObject = valueAsList; - IList list = null; - - if (type.IsArray) - { - list = (IList)ConstructorCache[type](jsonObject.Count); - int i = 0; - foreach (object o in jsonObject) - list[i++] = DeserializeObject(o, type.GetElementType()); - } - else if (ReflectionUtils.IsTypeGenericCollectionInterface(type) || ReflectionUtils.IsAssignableFrom(typeof(IList), type)) - { - Type innerType = ReflectionUtils.GetGenericListElementType(type); - list = (IList)(ConstructorCache[type] ?? ConstructorCache[typeof(List<>).MakeGenericType(innerType)])(jsonObject.Count); - foreach (object o in jsonObject) - list.Add(DeserializeObject(o, innerType)); - } - obj = list; - } - } - return obj; - } - if (ReflectionUtils.IsNullableType(type)) - { - // For nullable enums serialized as numbers - if (Nullable.GetUnderlyingType(type).IsEnum) - { - return Enum.ToObject(Nullable.GetUnderlyingType(type), value); - } - - return ReflectionUtils.ToNullableType(obj, type); - } - - return obj; - } - - protected virtual object SerializeEnum(Enum p) - { - return Convert.ToDouble(p, CultureInfo.InvariantCulture); - } - - [SuppressMessage("Microsoft.Design", "CA1007:UseGenericsWhereAppropriate", Justification="Need to support .NET 2")] - protected virtual bool TrySerializeKnownTypes(object input, out object output) - { - bool returnValue = true; - if (input is DateTime) - output = ((DateTime)input).ToUniversalTime().ToString(Iso8601Format[0], CultureInfo.InvariantCulture); - else if (input is DateTimeOffset) - output = ((DateTimeOffset)input).ToString("o"); - else if (input is Guid) - output = ((Guid)input).ToString("D"); - else if (input is Uri) - output = input.ToString(); - else if (input is TimeSpan) - output = ((TimeSpan)input).ToString("c"); - else - { - Enum inputEnum = input as Enum; - if (inputEnum != null) - output = SerializeEnum(inputEnum); - else - { - returnValue = false; - output = null; - } - } - return returnValue; - } - [SuppressMessage("Microsoft.Design", "CA1007:UseGenericsWhereAppropriate", Justification="Need to support .NET 2")] - protected virtual bool TrySerializeUnknownTypes(object input, out object output) - { - if (input == null) throw new ArgumentNullException("input"); - output = null; - Type type = input.GetType(); - if (type.FullName == null) - return false; - IDictionary obj = new JsonObject(); - IDictionary getters = GetCache[type]; - foreach (KeyValuePair getter in getters) - { - if (getter.Value != null) - obj.Add(MapClrMemberNameToJsonFieldName(getter.Key), getter.Value(input)); - } - output = obj; - return true; - } - } - -#if SIMPLE_JSON_DATACONTRACT - [GeneratedCode("simple-json", "1.0.0")] -#if SIMPLE_JSON_INTERNAL - internal -#else - public -#endif - class DataContractJsonSerializerStrategy : PocoJsonSerializerStrategy - { - public DataContractJsonSerializerStrategy() - { - GetCache = new ReflectionUtils.ThreadSafeDictionary>(GetterValueFactory); - SetCache = new ReflectionUtils.ThreadSafeDictionary>>(SetterValueFactory); - } - - internal override IDictionary GetterValueFactory(Type type) - { - bool hasDataContract = ReflectionUtils.GetAttribute(type, typeof(DataContractAttribute)) != null; - if (!hasDataContract) - return base.GetterValueFactory(type); - string jsonKey; - IDictionary result = new Dictionary(); - foreach (PropertyInfo propertyInfo in ReflectionUtils.GetProperties(type)) - { - if (propertyInfo.CanRead) - { - MethodInfo getMethod = ReflectionUtils.GetGetterMethodInfo(propertyInfo); - if (!getMethod.IsStatic && CanAdd(propertyInfo, out jsonKey)) - result[jsonKey] = ReflectionUtils.GetGetMethod(propertyInfo); - } - } - foreach (FieldInfo fieldInfo in ReflectionUtils.GetFields(type)) - { - if (!fieldInfo.IsStatic && CanAdd(fieldInfo, out jsonKey)) - result[jsonKey] = ReflectionUtils.GetGetMethod(fieldInfo); - } - return result; - } - - internal override IDictionary> SetterValueFactory(Type type) - { - bool hasDataContract = ReflectionUtils.GetAttribute(type, typeof(DataContractAttribute)) != null; - if (!hasDataContract) - return base.SetterValueFactory(type); - string jsonKey; - IDictionary> result = new Dictionary>(); - foreach (PropertyInfo propertyInfo in ReflectionUtils.GetProperties(type)) - { - if (propertyInfo.CanWrite) - { - MethodInfo setMethod = ReflectionUtils.GetSetterMethodInfo(propertyInfo); - if (!setMethod.IsStatic && CanAdd(propertyInfo, out jsonKey)) - result[jsonKey] = new KeyValuePair(propertyInfo.PropertyType, ReflectionUtils.GetSetMethod(propertyInfo)); - } - } - foreach (FieldInfo fieldInfo in ReflectionUtils.GetFields(type)) - { - if (!fieldInfo.IsInitOnly && !fieldInfo.IsStatic && CanAdd(fieldInfo, out jsonKey)) - result[jsonKey] = new KeyValuePair(fieldInfo.FieldType, ReflectionUtils.GetSetMethod(fieldInfo)); - } - // todo implement sorting for DATACONTRACT. - return result; - } - - private static bool CanAdd(MemberInfo info, out string jsonKey) - { - jsonKey = null; - if (ReflectionUtils.GetAttribute(info, typeof(IgnoreDataMemberAttribute)) != null) - return false; - DataMemberAttribute dataMemberAttribute = (DataMemberAttribute)ReflectionUtils.GetAttribute(info, typeof(DataMemberAttribute)); - if (dataMemberAttribute == null) - return false; - jsonKey = string.IsNullOrEmpty(dataMemberAttribute.Name) ? info.Name : dataMemberAttribute.Name; - return true; - } - } - -#endif - - namespace Reflection - { - // This class is meant to be copied into other libraries. So we want to exclude it from Code Analysis rules - // that might be in place in the target project. - [GeneratedCode("reflection-utils", "1.0.0")] -#if SIMPLE_JSON_REFLECTION_UTILS_PUBLIC - public -#else - internal -#endif - class ReflectionUtils - { - private static readonly object[] EmptyObjects = new object[] { }; - - public delegate object GetDelegate(object source); - public delegate void SetDelegate(object source, object value); - public delegate object ConstructorDelegate(params object[] args); - - public delegate TValue ThreadSafeDictionaryValueFactory(TKey key); - -#if SIMPLE_JSON_TYPEINFO - public static TypeInfo GetTypeInfo(Type type) - { - return type.GetTypeInfo(); - } -#else - public static Type GetTypeInfo(Type type) - { - return type; - } -#endif - - public static Attribute GetAttribute(MemberInfo info, Type type) - { -#if SIMPLE_JSON_TYPEINFO - if (info == null || type == null || !info.IsDefined(type)) - return null; - return info.GetCustomAttribute(type); -#else - if (info == null || type == null || !Attribute.IsDefined(info, type)) - return null; - return Attribute.GetCustomAttribute(info, type); -#endif - } - - public static Type GetGenericListElementType(Type type) - { - IEnumerable interfaces; -#if SIMPLE_JSON_TYPEINFO - interfaces = type.GetTypeInfo().ImplementedInterfaces; -#else - interfaces = type.GetInterfaces(); -#endif - foreach (Type implementedInterface in interfaces) - { - if (IsTypeGeneric(implementedInterface) && - implementedInterface.GetGenericTypeDefinition() == typeof (IList<>)) - { - return GetGenericTypeArguments(implementedInterface)[0]; - } - } - return GetGenericTypeArguments(type)[0]; - } - - public static Attribute GetAttribute(Type objectType, Type attributeType) - { - -#if SIMPLE_JSON_TYPEINFO - if (objectType == null || attributeType == null || !objectType.GetTypeInfo().IsDefined(attributeType)) - return null; - return objectType.GetTypeInfo().GetCustomAttribute(attributeType); -#else - if (objectType == null || attributeType == null || !Attribute.IsDefined(objectType, attributeType)) - return null; - return Attribute.GetCustomAttribute(objectType, attributeType); -#endif - } - - public static Type[] GetGenericTypeArguments(Type type) - { -#if SIMPLE_JSON_TYPEINFO - return type.GetTypeInfo().GenericTypeArguments; -#else - return type.GetGenericArguments(); -#endif - } - - public static bool IsTypeGeneric(Type type) - { - return GetTypeInfo(type).IsGenericType; - } - - public static bool IsTypeGenericCollectionInterface(Type type) - { - if (!IsTypeGeneric(type)) - return false; - - Type genericDefinition = type.GetGenericTypeDefinition(); - - return (genericDefinition == typeof(IList<>) - || genericDefinition == typeof(ICollection<>) - || genericDefinition == typeof(IEnumerable<>) -#if SIMPLE_JSON_READONLY_COLLECTIONS - || genericDefinition == typeof(IReadOnlyCollection<>) - || genericDefinition == typeof(IReadOnlyList<>) -#endif - ); - } - - public static bool IsAssignableFrom(Type type1, Type type2) - { - return GetTypeInfo(type1).IsAssignableFrom(GetTypeInfo(type2)); - } - - public static bool IsTypeDictionary(Type type) - { -#if SIMPLE_JSON_TYPEINFO - if (typeof(IDictionary<,>).GetTypeInfo().IsAssignableFrom(type.GetTypeInfo())) - return true; -#else - if (typeof(System.Collections.IDictionary).IsAssignableFrom(type)) - return true; -#endif - if (!GetTypeInfo(type).IsGenericType) - return false; - - Type genericDefinition = type.GetGenericTypeDefinition(); - return genericDefinition == typeof(IDictionary<,>); - } - - public static bool IsNullableType(Type type) - { - return GetTypeInfo(type).IsGenericType && type.GetGenericTypeDefinition() == typeof(Nullable<>); - } - - public static object ToNullableType(object obj, Type nullableType) - { - return obj == null ? null : Convert.ChangeType(obj, Nullable.GetUnderlyingType(nullableType), CultureInfo.InvariantCulture); - } - - public static bool IsValueType(Type type) - { - return GetTypeInfo(type).IsValueType; - } - - public static IEnumerable GetConstructors(Type type) - { -#if SIMPLE_JSON_TYPEINFO - return type.GetTypeInfo().DeclaredConstructors; -#else - const BindingFlags flags = BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic; - return type.GetConstructors(flags); -#endif - } - - public static ConstructorInfo GetConstructorInfo(Type type, params Type[] argsType) - { - IEnumerable constructorInfos = GetConstructors(type); - int i; - bool matches; - foreach (ConstructorInfo constructorInfo in constructorInfos) - { - ParameterInfo[] parameters = constructorInfo.GetParameters(); - if (argsType.Length != parameters.Length) - continue; - - i = 0; - matches = true; - foreach (ParameterInfo parameterInfo in constructorInfo.GetParameters()) - { - if (parameterInfo.ParameterType != argsType[i]) - { - matches = false; - break; - } - } - - if (matches) - return constructorInfo; - } - - return null; - } - - public static IEnumerable GetProperties(Type type) - { -#if SIMPLE_JSON_TYPEINFO - return type.GetRuntimeProperties(); -#else - return type.GetProperties(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Static); -#endif - } - - public static IEnumerable GetFields(Type type) - { -#if SIMPLE_JSON_TYPEINFO - return type.GetRuntimeFields(); -#else - return type.GetFields(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Static); -#endif - } - - public static MethodInfo GetGetterMethodInfo(PropertyInfo propertyInfo) - { -#if SIMPLE_JSON_TYPEINFO - return propertyInfo.GetMethod; -#else - return propertyInfo.GetGetMethod(true); -#endif - } - - public static MethodInfo GetSetterMethodInfo(PropertyInfo propertyInfo) - { -#if SIMPLE_JSON_TYPEINFO - return propertyInfo.SetMethod; -#else - return propertyInfo.GetSetMethod(true); -#endif - } - - public static ConstructorDelegate GetConstructor(ConstructorInfo constructorInfo) - { -#if SIMPLE_JSON_NO_LINQ_EXPRESSION - return GetConstructorByReflection(constructorInfo); -#else - return GetConstructorByExpression(constructorInfo); -#endif - } - - public static ConstructorDelegate GetConstructor(Type type, params Type[] argsType) - { -#if SIMPLE_JSON_NO_LINQ_EXPRESSION - return GetConstructorByReflection(type, argsType); -#else - return GetConstructorByExpression(type, argsType); -#endif - } - - public static ConstructorDelegate GetConstructorByReflection(ConstructorInfo constructorInfo) - { - return delegate(object[] args) { return constructorInfo.Invoke(args); }; - } - - public static ConstructorDelegate GetConstructorByReflection(Type type, params Type[] argsType) - { - ConstructorInfo constructorInfo = GetConstructorInfo(type, argsType); - - if (constructorInfo == null && argsType.Length == 0 && type.IsValueType) - { - // If it's a struct, then parameterless constructors are implicit - // We can always call Activator.CreateInstance in lieu of a zero-arg constructor - return args => Activator.CreateInstance(type); - } - - return constructorInfo == null ? null : GetConstructorByReflection(constructorInfo); - } - -#if !SIMPLE_JSON_NO_LINQ_EXPRESSION - - public static ConstructorDelegate GetConstructorByExpression(ConstructorInfo constructorInfo) - { - ParameterInfo[] paramsInfo = constructorInfo.GetParameters(); - ParameterExpression param = Expression.Parameter(typeof(object[]), "args"); - Expression[] argsExp = new Expression[paramsInfo.Length]; - for (int i = 0; i < paramsInfo.Length; i++) - { - Expression index = Expression.Constant(i); - Type paramType = paramsInfo[i].ParameterType; - Expression paramAccessorExp = Expression.ArrayIndex(param, index); - Expression paramCastExp = Expression.Convert(paramAccessorExp, paramType); - argsExp[i] = paramCastExp; - } - NewExpression newExp = Expression.New(constructorInfo, argsExp); - Expression> lambda = Expression.Lambda>(newExp, param); - Func compiledLambda = lambda.Compile(); - return delegate(object[] args) { return compiledLambda(args); }; - } - - public static ConstructorDelegate GetConstructorByExpression(Type type, params Type[] argsType) - { - ConstructorInfo constructorInfo = GetConstructorInfo(type, argsType); - return constructorInfo == null ? null : GetConstructorByExpression(constructorInfo); - } - -#endif - - public static GetDelegate GetGetMethod(PropertyInfo propertyInfo) - { -#if SIMPLE_JSON_NO_LINQ_EXPRESSION - return GetGetMethodByReflection(propertyInfo); -#else - return GetGetMethodByExpression(propertyInfo); -#endif - } - - public static GetDelegate GetGetMethod(FieldInfo fieldInfo) - { -#if SIMPLE_JSON_NO_LINQ_EXPRESSION - return GetGetMethodByReflection(fieldInfo); -#else - return GetGetMethodByExpression(fieldInfo); -#endif - } - - public static GetDelegate GetGetMethodByReflection(PropertyInfo propertyInfo) - { - MethodInfo methodInfo = GetGetterMethodInfo(propertyInfo); - return delegate(object source) { return methodInfo.Invoke(source, EmptyObjects); }; - } - - public static GetDelegate GetGetMethodByReflection(FieldInfo fieldInfo) - { - return delegate(object source) { return fieldInfo.GetValue(source); }; - } - -#if !SIMPLE_JSON_NO_LINQ_EXPRESSION - - public static GetDelegate GetGetMethodByExpression(PropertyInfo propertyInfo) - { - MethodInfo getMethodInfo = GetGetterMethodInfo(propertyInfo); - ParameterExpression instance = Expression.Parameter(typeof(object), "instance"); - UnaryExpression instanceCast = (!IsValueType(propertyInfo.DeclaringType)) ? Expression.TypeAs(instance, propertyInfo.DeclaringType) : Expression.Convert(instance, propertyInfo.DeclaringType); - Func compiled = Expression.Lambda>(Expression.TypeAs(Expression.Call(instanceCast, getMethodInfo), typeof(object)), instance).Compile(); - return delegate(object source) { return compiled(source); }; - } - - public static GetDelegate GetGetMethodByExpression(FieldInfo fieldInfo) - { - ParameterExpression instance = Expression.Parameter(typeof(object), "instance"); - MemberExpression member = Expression.Field(Expression.Convert(instance, fieldInfo.DeclaringType), fieldInfo); - GetDelegate compiled = Expression.Lambda(Expression.Convert(member, typeof(object)), instance).Compile(); - return delegate(object source) { return compiled(source); }; - } - -#endif - - public static SetDelegate GetSetMethod(PropertyInfo propertyInfo) - { -#if SIMPLE_JSON_NO_LINQ_EXPRESSION - return GetSetMethodByReflection(propertyInfo); -#else - return GetSetMethodByExpression(propertyInfo); -#endif - } - - public static SetDelegate GetSetMethod(FieldInfo fieldInfo) - { -#if SIMPLE_JSON_NO_LINQ_EXPRESSION - return GetSetMethodByReflection(fieldInfo); -#else - return GetSetMethodByExpression(fieldInfo); -#endif - } - - public static SetDelegate GetSetMethodByReflection(PropertyInfo propertyInfo) - { - MethodInfo methodInfo = GetSetterMethodInfo(propertyInfo); - return delegate(object source, object value) { methodInfo.Invoke(source, new object[] { value }); }; - } - - public static SetDelegate GetSetMethodByReflection(FieldInfo fieldInfo) - { - return delegate(object source, object value) { fieldInfo.SetValue(source, value); }; - } - -#if !SIMPLE_JSON_NO_LINQ_EXPRESSION - - public static SetDelegate GetSetMethodByExpression(PropertyInfo propertyInfo) - { - MethodInfo setMethodInfo = GetSetterMethodInfo(propertyInfo); - ParameterExpression instance = Expression.Parameter(typeof(object), "instance"); - ParameterExpression value = Expression.Parameter(typeof(object), "value"); - UnaryExpression instanceCast = (!IsValueType(propertyInfo.DeclaringType)) ? Expression.TypeAs(instance, propertyInfo.DeclaringType) : Expression.Convert(instance, propertyInfo.DeclaringType); - UnaryExpression valueCast = (!IsValueType(propertyInfo.PropertyType)) ? Expression.TypeAs(value, propertyInfo.PropertyType) : Expression.Convert(value, propertyInfo.PropertyType); - Action compiled = Expression.Lambda>(Expression.Call(instanceCast, setMethodInfo, valueCast), new ParameterExpression[] { instance, value }).Compile(); - return delegate(object source, object val) { compiled(source, val); }; - } - - public static SetDelegate GetSetMethodByExpression(FieldInfo fieldInfo) - { - ParameterExpression instance = Expression.Parameter(typeof(object), "instance"); - ParameterExpression value = Expression.Parameter(typeof(object), "value"); - Action compiled = Expression.Lambda>( - Assign(Expression.Field(Expression.Convert(instance, fieldInfo.DeclaringType), fieldInfo), Expression.Convert(value, fieldInfo.FieldType)), instance, value).Compile(); - return delegate(object source, object val) { compiled(source, val); }; - } - - public static BinaryExpression Assign(Expression left, Expression right) - { -#if SIMPLE_JSON_TYPEINFO - return Expression.Assign(left, right); -#else - MethodInfo assign = typeof(Assigner<>).MakeGenericType(left.Type).GetMethod("Assign"); - BinaryExpression assignExpr = Expression.Add(left, right, assign); - return assignExpr; -#endif - } - - private static class Assigner - { - public static T Assign(ref T left, T right) - { - return (left = right); - } - } - -#endif - - public sealed class ThreadSafeDictionary : IDictionary - { - private readonly object _lock = new object(); - private readonly ThreadSafeDictionaryValueFactory _valueFactory; - private Dictionary _dictionary; - - public ThreadSafeDictionary(ThreadSafeDictionaryValueFactory valueFactory) - { - _valueFactory = valueFactory; - } - - private TValue Get(TKey key) - { - if (_dictionary == null) - return AddValue(key); - TValue value; - if (!_dictionary.TryGetValue(key, out value)) - return AddValue(key); - return value; - } - - private TValue AddValue(TKey key) - { - TValue value = _valueFactory(key); - lock (_lock) - { - if (_dictionary == null) - { - _dictionary = new Dictionary(); - _dictionary[key] = value; - } - else - { - TValue val; - if (_dictionary.TryGetValue(key, out val)) - return val; - Dictionary dict = new Dictionary(_dictionary); - dict[key] = value; - _dictionary = dict; - } - } - return value; - } - - public void Add(TKey key, TValue value) - { - throw new NotImplementedException(); - } - - public bool ContainsKey(TKey key) - { - return _dictionary.ContainsKey(key); - } - - public ICollection Keys - { - get { return _dictionary.Keys; } - } - - public bool Remove(TKey key) - { - throw new NotImplementedException(); - } - - public bool TryGetValue(TKey key, out TValue value) - { - value = this[key]; - return true; - } - - public ICollection Values - { - get { return _dictionary.Values; } - } - - public TValue this[TKey key] - { - get { return Get(key); } - set { throw new NotImplementedException(); } - } - - public void Add(KeyValuePair item) - { - throw new NotImplementedException(); - } - - public void Clear() - { - throw new NotImplementedException(); - } - - public bool Contains(KeyValuePair item) - { - throw new NotImplementedException(); - } - - public void CopyTo(KeyValuePair[] array, int arrayIndex) - { - throw new NotImplementedException(); - } - - public int Count - { - get { return _dictionary.Count; } - } - - public bool IsReadOnly - { - get { throw new NotImplementedException(); } - } - - public bool Remove(KeyValuePair item) - { - throw new NotImplementedException(); - } - - public IEnumerator> GetEnumerator() - { - return _dictionary.GetEnumerator(); - } - - System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() - { - return _dictionary.GetEnumerator(); - } - } - - } - } -} -// ReSharper restore LoopCanBeConvertedToQuery -// ReSharper restore RedundantExplicitArrayCreation -// ReSharper restore SuggestUseVarKeywordEvident diff --git a/src/JSInterop/Microsoft.JSInterop/src/JsonSerializerOptionsProvider.cs b/src/JSInterop/Microsoft.JSInterop/src/JsonSerializerOptionsProvider.cs new file mode 100644 index 0000000000..0292039eaf --- /dev/null +++ b/src/JSInterop/Microsoft.JSInterop/src/JsonSerializerOptionsProvider.cs @@ -0,0 +1,15 @@ +// 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.Text.Json.Serialization; + +namespace Microsoft.JSInterop +{ + internal static class JsonSerializerOptionsProvider + { + public static readonly JsonSerializerOptions Options = new JsonSerializerOptions + { + PropertyNamingPolicy = JsonNamingPolicy.CamelCase, + }; + } +} diff --git a/src/JSInterop/Microsoft.JSInterop/src/Microsoft.JSInterop.csproj b/src/JSInterop/Microsoft.JSInterop/src/Microsoft.JSInterop.csproj index f92b8d457d..bc912b97cc 100644 --- a/src/JSInterop/Microsoft.JSInterop/src/Microsoft.JSInterop.csproj +++ b/src/JSInterop/Microsoft.JSInterop/src/Microsoft.JSInterop.csproj @@ -1,4 +1,4 @@ - + netstandard2.0 @@ -8,6 +8,10 @@ true + + + + diff --git a/src/JSInterop/Microsoft.JSInterop/test/DotNetDispatcherTest.cs b/src/JSInterop/Microsoft.JSInterop/test/DotNetDispatcherTest.cs index e3f91a6fd0..c65b4e4680 100644 --- a/src/JSInterop/Microsoft.JSInterop/test/DotNetDispatcherTest.cs +++ b/src/JSInterop/Microsoft.JSInterop/test/DotNetDispatcherTest.cs @@ -3,6 +3,8 @@ using System; using System.Linq; +using System.Text.Json; +using System.Text.Json.Serialization; using System.Threading.Tasks; using Xunit; @@ -10,10 +12,7 @@ namespace Microsoft.JSInterop.Tests { public class DotNetDispatcherTest { - private readonly static string thisAssemblyName - = typeof(DotNetDispatcherTest).Assembly.GetName().Name; - private readonly TestJSRuntime jsRuntime - = new TestJSRuntime(); + private readonly static string thisAssemblyName = typeof(DotNetDispatcherTest).Assembly.GetName().Name; [Fact] public void CannotInvokeWithEmptyAssemblyName() @@ -24,7 +23,7 @@ namespace Microsoft.JSInterop.Tests }); Assert.StartsWith("Cannot be null, empty, or whitespace.", ex.Message); - Assert.Equal("assemblyName", ex.ParamName); + Assert.Equal("AssemblyName", ex.ParamName); } [Fact] @@ -73,7 +72,7 @@ namespace Microsoft.JSInterop.Tests Assert.Equal($"The assembly '{thisAssemblyName}' does not contain a public method with [JSInvokableAttribute(\"{methodIdentifier}\")].", ex.Message); } - [Fact(Skip = "https://github.com/aspnet/AspNetCore-Internal/issues/1733")] + [Fact] public Task CanInvokeStaticVoidMethod() => WithJSRuntime(jsRuntime => { // Arrange/Act @@ -90,7 +89,7 @@ namespace Microsoft.JSInterop.Tests { // Arrange/Act var resultJson = DotNetDispatcher.Invoke(thisAssemblyName, "InvocableStaticNonVoid", default, null); - var result = Json.Deserialize(resultJson); + var result = JsonSerializer.Parse(resultJson, JsonSerializerOptionsProvider.Options); // Assert Assert.Equal("Test", result.StringVal); @@ -102,50 +101,81 @@ namespace Microsoft.JSInterop.Tests { // Arrange/Act var resultJson = DotNetDispatcher.Invoke(thisAssemblyName, nameof(SomePublicType.InvokableMethodWithoutCustomIdentifier), default, null); - var result = Json.Deserialize(resultJson); + var result = JsonSerializer.Parse(resultJson, JsonSerializerOptionsProvider.Options); // Assert Assert.Equal("InvokableMethodWithoutCustomIdentifier", result.StringVal); Assert.Equal(456, result.IntVal); }); - [Fact(Skip = "https://github.com/aspnet/AspNetCore-Internal/issues/1733")] + [Fact] public Task CanInvokeStaticWithParams() => WithJSRuntime(jsRuntime => { // Arrange: Track a .NET object to use as an arg var arg3 = new TestDTO { IntVal = 999, StringVal = "My string" }; - jsRuntime.Invoke("unimportant", new DotNetObjectRef(arg3)); + var objectRef = DotNetObjectRef.Create(arg3); + jsRuntime.Invoke("unimportant", objectRef); // Arrange: Remaining args - var argsJson = Json.Serialize(new object[] { + var argsJson = JsonSerializer.ToString(new object[] + { new TestDTO { StringVal = "Another string", IntVal = 456 }, new[] { 100, 200 }, - "__dotNetObject:1" - }); + objectRef + }, JsonSerializerOptionsProvider.Options); // Act var resultJson = DotNetDispatcher.Invoke(thisAssemblyName, "InvocableStaticWithParams", default, argsJson); - var result = Json.Deserialize(resultJson); + var result = JsonDocument.Parse(resultJson); + var root = result.RootElement; // Assert: First result value marshalled via JSON - var resultDto1 = (TestDTO)jsRuntime.ArgSerializerStrategy.DeserializeObject(result[0], typeof(TestDTO)); + var resultDto1 = JsonSerializer.Parse(root[0].GetRawText(), JsonSerializerOptionsProvider.Options); + Assert.Equal("ANOTHER STRING", resultDto1.StringVal); Assert.Equal(756, resultDto1.IntVal); // Assert: Second result value marshalled by ref - var resultDto2Ref = (string)result[1]; - Assert.Equal("__dotNetObject:2", resultDto2Ref); - var resultDto2 = (TestDTO)jsRuntime.ArgSerializerStrategy.FindDotNetObject(2); + var resultDto2Ref = root[1]; + Assert.False(resultDto2Ref.TryGetProperty(nameof(TestDTO.StringVal), out _)); + Assert.False(resultDto2Ref.TryGetProperty(nameof(TestDTO.IntVal), out _)); + + Assert.True(resultDto2Ref.TryGetProperty(DotNetDispatcher.DotNetObjectRefKey, out var property)); + var resultDto2 = Assert.IsType(DotNetObjectRefManager.Current.FindDotNetObject(property.GetInt64())); Assert.Equal("MY STRING", resultDto2.StringVal); Assert.Equal(1299, resultDto2.IntVal); }); - [Fact(Skip = "https://github.com/aspnet/AspNetCore-Internal/issues/1733")] + [Fact] + public Task InvokingWithIncorrectUseOfDotNetObjectRefThrows() => WithJSRuntime(jsRuntime => + { + // Arrange + var method = nameof(SomePublicType.IncorrectDotNetObjectRefUsage); + var arg3 = new TestDTO { IntVal = 999, StringVal = "My string" }; + var objectRef = DotNetObjectRef.Create(arg3); + jsRuntime.Invoke("unimportant", objectRef); + + // Arrange: Remaining args + var argsJson = JsonSerializer.ToString(new object[] + { + new TestDTO { StringVal = "Another string", IntVal = 456 }, + new[] { 100, 200 }, + objectRef + }, JsonSerializerOptionsProvider.Options); + + // Act & Assert + var ex = Assert.Throws(() => + DotNetDispatcher.Invoke(thisAssemblyName, method, default, argsJson)); + Assert.Equal($"In call to '{method}', parameter of type '{nameof(TestDTO)}' at index 3 must be declared as type 'DotNetObjectRef' to receive the incoming value.", ex.Message); + }); + + [Fact] public Task CanInvokeInstanceVoidMethod() => WithJSRuntime(jsRuntime => { // Arrange: Track some instance var targetInstance = new SomePublicType(); - jsRuntime.Invoke("unimportant", new DotNetObjectRef(targetInstance)); + var objectRef = DotNetObjectRef.Create(targetInstance); + jsRuntime.Invoke("unimportant", objectRef); // Act var resultJson = DotNetDispatcher.Invoke(null, "InvokableInstanceVoid", 1, null); @@ -155,12 +185,13 @@ namespace Microsoft.JSInterop.Tests Assert.True(targetInstance.DidInvokeMyInvocableInstanceVoid); }); - [Fact(Skip = "https://github.com/aspnet/AspNetCore-Internal/issues/1733")] + [Fact] public Task CanInvokeBaseInstanceVoidMethod() => WithJSRuntime(jsRuntime => { // Arrange: Track some instance var targetInstance = new DerivedClass(); - jsRuntime.Invoke("unimportant", new DotNetObjectRef(targetInstance)); + var objectRef = DotNetObjectRef.Create(targetInstance); + jsRuntime.Invoke("unimportant", objectRef); // Act var resultJson = DotNetDispatcher.Invoke(null, "BaseClassInvokableInstanceVoid", 1, null); @@ -178,7 +209,7 @@ namespace Microsoft.JSInterop.Tests // Arrange: Track some instance, then dispose it var targetInstance = new SomePublicType(); - var objectRef = new DotNetObjectRef(targetInstance); + var objectRef = DotNetObjectRef.Create(targetInstance); jsRuntime.Invoke("unimportant", objectRef); objectRef.Dispose(); @@ -196,7 +227,7 @@ namespace Microsoft.JSInterop.Tests // Arrange: Track some instance, then dispose it var targetInstance = new SomePublicType(); - var objectRef = new DotNetObjectRef(targetInstance); + var objectRef = DotNetObjectRef.Create(targetInstance); jsRuntime.Invoke("unimportant", objectRef); DotNetDispatcher.ReleaseDotNetObject(1); @@ -206,23 +237,23 @@ namespace Microsoft.JSInterop.Tests Assert.StartsWith("There is no tracked object with id '1'.", ex.Message); }); - [Fact(Skip = "https://github.com/aspnet/AspNetCore-Internal/issues/1733")] + [Fact] public Task CanInvokeInstanceMethodWithParams() => WithJSRuntime(jsRuntime => { // Arrange: Track some instance plus another object we'll pass as a param var targetInstance = new SomePublicType(); var arg2 = new TestDTO { IntVal = 1234, StringVal = "My string" }; jsRuntime.Invoke("unimportant", - new DotNetObjectRef(targetInstance), - new DotNetObjectRef(arg2)); - var argsJson = "[\"myvalue\",\"__dotNetObject:2\"]"; + DotNetObjectRef.Create(targetInstance), + DotNetObjectRef.Create(arg2)); + var argsJson = "[\"myvalue\",{\"__dotNetObject\":2}]"; // Act var resultJson = DotNetDispatcher.Invoke(null, "InvokableInstanceMethod", 1, argsJson); // Assert - Assert.Equal("[\"You passed myvalue\",\"__dotNetObject:3\"]", resultJson); - var resultDto = (TestDTO)jsRuntime.ArgSerializerStrategy.FindDotNetObject(3); + Assert.Equal("[\"You passed myvalue\",{\"__dotNetObject\":3}]", resultJson); + var resultDto = (TestDTO)jsRuntime.ObjectRefManager.FindDotNetObject(3); Assert.Equal(1235, resultDto.IntVal); Assert.Equal("MY STRING", resultDto.StringVal); }); @@ -231,7 +262,7 @@ namespace Microsoft.JSInterop.Tests public void CannotInvokeWithIncorrectNumberOfParams() { // Arrange - var argsJson = Json.Serialize(new object[] { 1, 2, 3, 4 }); + var argsJson = JsonSerializer.ToString(new object[] { 1, 2, 3, 4 }, JsonSerializerOptionsProvider.Options); // Act/Assert var ex = Assert.Throws(() => @@ -242,50 +273,50 @@ namespace Microsoft.JSInterop.Tests Assert.Equal("In call to 'InvocableStaticWithParams', expected 3 parameters but received 4.", ex.Message); } - [Fact(Skip = "https://github.com/aspnet/AspNetCore-Internal/issues/1733")] + [Fact] public Task CanInvokeAsyncMethod() => WithJSRuntime(async jsRuntime => { // Arrange: Track some instance plus another object we'll pass as a param var targetInstance = new SomePublicType(); var arg2 = new TestDTO { IntVal = 1234, StringVal = "My string" }; - jsRuntime.Invoke("unimportant", new DotNetObjectRef(targetInstance), new DotNetObjectRef(arg2)); + var arg1Ref = DotNetObjectRef.Create(targetInstance); + var arg2Ref = DotNetObjectRef.Create(arg2); + jsRuntime.Invoke("unimportant", arg1Ref, arg2Ref); // Arrange: all args - var argsJson = Json.Serialize(new object[] + var argsJson = JsonSerializer.ToString(new object[] { new TestDTO { IntVal = 1000, StringVal = "String via JSON" }, - "__dotNetObject:2" - }); + arg2Ref, + }, JsonSerializerOptionsProvider.Options); // Act var callId = "123"; var resultTask = jsRuntime.NextInvocationTask; DotNetDispatcher.BeginInvoke(callId, null, "InvokableAsyncMethod", 1, argsJson); await resultTask; - var result = Json.Deserialize(jsRuntime.LastInvocationArgsJson); - var resultValue = (SimpleJson.JsonArray)result[2]; + var result = JsonDocument.Parse(jsRuntime.LastInvocationArgsJson).RootElement; + var resultValue = result[2]; // Assert: Correct info to complete the async call Assert.Equal(0, jsRuntime.LastInvocationAsyncHandle); // 0 because it doesn't want a further callback from JS to .NET Assert.Equal("DotNet.jsCallDispatcher.endInvokeDotNetFromJS", jsRuntime.LastInvocationIdentifier); - Assert.Equal(3, result.Count); - Assert.Equal(callId, result[0]); - Assert.True((bool)result[1]); // Success flag + Assert.Equal(3, result.GetArrayLength()); + Assert.Equal(callId, result[0].GetString()); + Assert.True(result[1].GetBoolean()); // Success flag // Assert: First result value marshalled via JSON - var resultDto1 = (TestDTO)jsRuntime.ArgSerializerStrategy.DeserializeObject(resultValue[0], typeof(TestDTO)); + var resultDto1 = JsonSerializer.Parse(resultValue[0].GetRawText(), JsonSerializerOptionsProvider.Options); Assert.Equal("STRING VIA JSON", resultDto1.StringVal); Assert.Equal(2000, resultDto1.IntVal); // Assert: Second result value marshalled by ref - var resultDto2Ref = (string)resultValue[1]; - Assert.Equal("__dotNetObject:3", resultDto2Ref); - var resultDto2 = (TestDTO)jsRuntime.ArgSerializerStrategy.FindDotNetObject(3); + var resultDto2Ref = JsonSerializer.Parse>(resultValue[1].GetRawText(), JsonSerializerOptionsProvider.Options); + var resultDto2 = resultDto2Ref.Value; Assert.Equal("MY STRING", resultDto2.StringVal); Assert.Equal(2468, resultDto2.IntVal); }); - [Fact] public Task CanInvokeSyncThrowingMethod() => WithJSRuntime(async jsRuntime => { @@ -299,13 +330,13 @@ namespace Microsoft.JSInterop.Tests await resultTask; // This won't throw, it sets properties on the jsRuntime. // Assert - var result = Json.Deserialize(jsRuntime.LastInvocationArgsJson); - Assert.Equal(callId, result[0]); - Assert.False((bool)result[1]); // Fails + var result = JsonDocument.Parse(jsRuntime.LastInvocationArgsJson).RootElement; + Assert.Equal(callId, result[0].GetString()); + Assert.False(result[1].GetBoolean()); // Fails // Make sure the method that threw the exception shows up in the call stack // https://github.com/aspnet/AspNetCore/issues/8612 - var exception = (string)result[2]; + var exception = result[2].GetString(); Assert.Contains(nameof(ThrowingClass.ThrowingMethod), exception); }); @@ -322,17 +353,16 @@ namespace Microsoft.JSInterop.Tests await resultTask; // This won't throw, it sets properties on the jsRuntime. // Assert - var result = Json.Deserialize(jsRuntime.LastInvocationArgsJson); - Assert.Equal(callId, result[0]); - Assert.False((bool)result[1]); // Fails + var result = JsonDocument.Parse(jsRuntime.LastInvocationArgsJson).RootElement; + Assert.Equal(callId, result[0].GetString()); + Assert.False(result[1].GetBoolean()); // Fails // Make sure the method that threw the exception shows up in the call stack // https://github.com/aspnet/AspNetCore/issues/8612 - var exception = (string)result[2]; + var exception = result[2].GetString(); Assert.Contains(nameof(ThrowingClass.AsyncThrowingMethod), exception); }); - Task WithJSRuntime(Action testCode) { return WithJSRuntime(jsRuntime => @@ -379,7 +409,7 @@ namespace Microsoft.JSInterop.Tests => new TestDTO { StringVal = "Test", IntVal = 123 }; [JSInvokable("InvocableStaticWithParams")] - public static object[] MyInvocableWithParams(TestDTO dtoViaJson, int[] incrementAmounts, TestDTO dtoByRef) + public static object[] MyInvocableWithParams(TestDTO dtoViaJson, int[] incrementAmounts, DotNetObjectRef dtoByRef) => new object[] { new TestDTO // Return via JSON marshalling @@ -387,13 +417,17 @@ namespace Microsoft.JSInterop.Tests StringVal = dtoViaJson.StringVal.ToUpperInvariant(), IntVal = dtoViaJson.IntVal + incrementAmounts.Sum() }, - new DotNetObjectRef(new TestDTO // Return by ref + DotNetObjectRef.Create(new TestDTO // Return by ref { - StringVal = dtoByRef.StringVal.ToUpperInvariant(), - IntVal = dtoByRef.IntVal + incrementAmounts.Sum() + StringVal = dtoByRef.Value.StringVal.ToUpperInvariant(), + IntVal = dtoByRef.Value.IntVal + incrementAmounts.Sum() }) }; + [JSInvokable(nameof(IncorrectDotNetObjectRefUsage))] + public static object[] IncorrectDotNetObjectRefUsage(TestDTO dtoViaJson, int[] incrementAmounts, TestDTO dtoByRef) + => throw new InvalidOperationException("Shouldn't be called"); + [JSInvokable] public static TestDTO InvokableMethodWithoutCustomIdentifier() => new TestDTO { StringVal = "InvokableMethodWithoutCustomIdentifier", IntVal = 456 }; @@ -405,14 +439,15 @@ namespace Microsoft.JSInterop.Tests } [JSInvokable] - public object[] InvokableInstanceMethod(string someString, TestDTO someDTO) + public object[] InvokableInstanceMethod(string someString, DotNetObjectRef someDTORef) { + var someDTO = someDTORef.Value; // Returning an array to make the point that object references // can be embedded anywhere in the result return new object[] { $"You passed {someString}", - new DotNetObjectRef(new TestDTO + DotNetObjectRef.Create(new TestDTO { IntVal = someDTO.IntVal + 1, StringVal = someDTO.StringVal.ToUpperInvariant() @@ -421,9 +456,10 @@ namespace Microsoft.JSInterop.Tests } [JSInvokable] - public async Task InvokableAsyncMethod(TestDTO dtoViaJson, TestDTO dtoByRef) + public async Task InvokableAsyncMethod(TestDTO dtoViaJson, DotNetObjectRef dtoByRefWrapper) { await Task.Delay(50); + var dtoByRef = dtoByRefWrapper.Value; return new object[] { new TestDTO // Return via JSON @@ -431,7 +467,7 @@ namespace Microsoft.JSInterop.Tests StringVal = dtoViaJson.StringVal.ToUpperInvariant(), IntVal = dtoViaJson.IntVal * 2, }, - new DotNetObjectRef(new TestDTO // Return by ref + DotNetObjectRef.Create(new TestDTO // Return by ref { StringVal = dtoByRef.StringVal.ToUpperInvariant(), IntVal = dtoByRef.IntVal * 2, diff --git a/src/JSInterop/Microsoft.JSInterop/test/DotNetObjectRefTest.cs b/src/JSInterop/Microsoft.JSInterop/test/DotNetObjectRefTest.cs index 1bdec6d465..2b46831a16 100644 --- a/src/JSInterop/Microsoft.JSInterop/test/DotNetObjectRefTest.cs +++ b/src/JSInterop/Microsoft.JSInterop/test/DotNetObjectRefTest.cs @@ -2,7 +2,6 @@ // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; -using System.Collections.Generic; using System.Threading.Tasks; using Xunit; @@ -11,58 +10,44 @@ namespace Microsoft.JSInterop.Tests public class DotNetObjectRefTest { [Fact] - public void CanAccessValue() + public Task CanAccessValue() => WithJSRuntime(_ => { var obj = new object(); - Assert.Same(obj, new DotNetObjectRef(obj).Value); - } + Assert.Same(obj, DotNetObjectRef.Create(obj).Value); + }); [Fact] - public void CanAssociateWithSameRuntimeMultipleTimes() - { - var objRef = new DotNetObjectRef(new object()); - var jsRuntime = new TestJsRuntime(); - objRef.EnsureAttachedToJsRuntime(jsRuntime); - objRef.EnsureAttachedToJsRuntime(jsRuntime); - } - - [Fact] - public void CannotAssociateWithDifferentRuntimes() - { - var objRef = new DotNetObjectRef(new object()); - var jsRuntime1 = new TestJsRuntime(); - var jsRuntime2 = new TestJsRuntime(); - objRef.EnsureAttachedToJsRuntime(jsRuntime1); - - var ex = Assert.Throws( - () => objRef.EnsureAttachedToJsRuntime(jsRuntime2)); - Assert.Contains("Do not attempt to re-use", ex.Message); - } - - [Fact] - public void NotifiesAssociatedJsRuntimeOfDisposal() + public Task NotifiesAssociatedJsRuntimeOfDisposal() => WithJSRuntime(jsRuntime => { // Arrange - var objRef = new DotNetObjectRef(new object()); - var jsRuntime = new TestJsRuntime(); - objRef.EnsureAttachedToJsRuntime(jsRuntime); + var objRef = DotNetObjectRef.Create(new object()); + var trackingId = objRef.__dotNetObject; // Act objRef.Dispose(); // Assert - Assert.Equal(new[] { objRef }, jsRuntime.UntrackedRefs); + var ex = Assert.Throws(() => jsRuntime.ObjectRefManager.FindDotNetObject(trackingId)); + Assert.StartsWith("There is no tracked object with id '1'.", ex.Message); + }); + + class TestJSRuntime : JSRuntimeBase + { + protected override void BeginInvokeJS(long asyncHandle, string identifier, string argsJson) + { + throw new NotImplementedException(); + } } - class TestJsRuntime : IJSRuntime + async Task WithJSRuntime(Action testCode) { - public List UntrackedRefs = new List(); + // Since the tests rely on the asynclocal JSRuntime.Current, ensure we + // are on a distinct async context with a non-null JSRuntime.Current + await Task.Yield(); - public Task InvokeAsync(string identifier, params object[] args) - => throw new NotImplementedException(); - - public void UntrackObjectRef(DotNetObjectRef dotNetObjectRef) - => UntrackedRefs.Add(dotNetObjectRef); + var runtime = new TestJSRuntime(); + JSRuntime.SetCurrentJSRuntime(runtime); + testCode(runtime); } } } diff --git a/src/JSInterop/Microsoft.JSInterop/test/JSInProcessRuntimeBaseTest.cs b/src/JSInterop/Microsoft.JSInterop/test/JSInProcessRuntimeBaseTest.cs index a13d53677a..dda3f74184 100644 --- a/src/JSInterop/Microsoft.JSInterop/test/JSInProcessRuntimeBaseTest.cs +++ b/src/JSInterop/Microsoft.JSInterop/test/JSInProcessRuntimeBaseTest.cs @@ -10,15 +10,15 @@ namespace Microsoft.JSInterop.Tests { public class JSInProcessRuntimeBaseTest { - [Fact(Skip = "https://github.com/aspnet/AspNetCore-Internal/issues/1807#issuecomment-470756811")] + [Fact] public void DispatchesSyncCallsAndDeserializesResults() { // Arrange var runtime = new TestJSInProcessRuntime { - NextResultJson = Json.Serialize( - new TestDTO { IntValue = 123, StringValue = "Hello" }) + NextResultJson = "{\"intValue\":123,\"stringValue\":\"Hello\"}" }; + JSRuntime.SetCurrentJSRuntime(runtime); // Act var syncResult = runtime.Invoke("test identifier 1", "arg1", 123, true); @@ -36,18 +36,19 @@ namespace Microsoft.JSInterop.Tests { // Arrange var runtime = new TestJSInProcessRuntime { NextResultJson = null }; + JSRuntime.SetCurrentJSRuntime(runtime); var obj1 = new object(); var obj2 = new object(); var obj3 = new object(); // Act // Showing we can pass the DotNetObject either as top-level args or nested - var syncResult = runtime.Invoke("test identifier", - new DotNetObjectRef(obj1), + var syncResult = runtime.Invoke>("test identifier", + DotNetObjectRef.Create(obj1), new Dictionary { - { "obj2", new DotNetObjectRef(obj2) }, - { "obj3", new DotNetObjectRef(obj3) } + { "obj2", DotNetObjectRef.Create(obj2) }, + { "obj3", DotNetObjectRef.Create(obj3) }, }); // Assert: Handles null result string @@ -56,12 +57,12 @@ namespace Microsoft.JSInterop.Tests // Assert: Serialized as expected var call = runtime.InvokeCalls.Single(); Assert.Equal("test identifier", call.Identifier); - Assert.Equal("[\"__dotNetObject:1\",{\"obj2\":\"__dotNetObject:2\",\"obj3\":\"__dotNetObject:3\"}]", call.ArgsJson); + Assert.Equal("[{\"__dotNetObject\":1},{\"obj2\":{\"__dotNetObject\":2},\"obj3\":{\"__dotNetObject\":3}}]", call.ArgsJson); // Assert: Objects were tracked - Assert.Same(obj1, runtime.ArgSerializerStrategy.FindDotNetObject(1)); - Assert.Same(obj2, runtime.ArgSerializerStrategy.FindDotNetObject(2)); - Assert.Same(obj3, runtime.ArgSerializerStrategy.FindDotNetObject(3)); + Assert.Same(obj1, runtime.ObjectRefManager.FindDotNetObject(1)); + Assert.Same(obj2, runtime.ObjectRefManager.FindDotNetObject(2)); + Assert.Same(obj3, runtime.ObjectRefManager.FindDotNetObject(3)); } [Fact] @@ -70,20 +71,22 @@ namespace Microsoft.JSInterop.Tests // Arrange var runtime = new TestJSInProcessRuntime { - NextResultJson = "[\"__dotNetObject:2\",\"__dotNetObject:1\"]" + NextResultJson = "[{\"__dotNetObject\":2},{\"__dotNetObject\":1}]" }; + JSRuntime.SetCurrentJSRuntime(runtime); var obj1 = new object(); var obj2 = new object(); // Act - var syncResult = runtime.Invoke("test identifier", - new DotNetObjectRef(obj1), + var syncResult = runtime.Invoke[]>( + "test identifier", + DotNetObjectRef.Create(obj1), "some other arg", - new DotNetObjectRef(obj2)); + DotNetObjectRef.Create(obj2)); var call = runtime.InvokeCalls.Single(); // Assert - Assert.Equal(new[] { obj2, obj1 }, syncResult); + Assert.Equal(new[] { obj2, obj1 }, syncResult.Select(r => r.Value)); } class TestDTO diff --git a/src/JSInterop/Microsoft.JSInterop/test/JSRuntimeBaseTest.cs b/src/JSInterop/Microsoft.JSInterop/test/JSRuntimeBaseTest.cs index ab048e812f..82126836d8 100644 --- a/src/JSInterop/Microsoft.JSInterop/test/JSRuntimeBaseTest.cs +++ b/src/JSInterop/Microsoft.JSInterop/test/JSRuntimeBaseTest.cs @@ -1,10 +1,11 @@ // 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 Microsoft.JSInterop.Internal; using System; using System.Collections.Generic; using System.Linq; +using System.Text.Json; +using Microsoft.JSInterop.Internal; using Xunit; namespace Microsoft.JSInterop.Tests @@ -47,12 +48,13 @@ namespace Microsoft.JSInterop.Tests var task = runtime.InvokeAsync("test identifier", Array.Empty()); Assert.False(unrelatedTask.IsCompleted); Assert.False(task.IsCompleted); + using var jsonDocument = JsonDocument.Parse("\"my result\""); // Act/Assert: Task can be completed runtime.OnEndInvoke( runtime.BeginInvokeCalls[1].AsyncHandle, /* succeeded: */ true, - "my result"); + new JSAsyncCallResult(jsonDocument, jsonDocument.RootElement)); Assert.False(unrelatedTask.IsCompleted); Assert.True(task.IsCompleted); Assert.Equal("my result", task.Result); @@ -69,12 +71,13 @@ namespace Microsoft.JSInterop.Tests var task = runtime.InvokeAsync("test identifier", Array.Empty()); Assert.False(unrelatedTask.IsCompleted); Assert.False(task.IsCompleted); + using var jsonDocument = JsonDocument.Parse("\"This is a test exception\""); // Act/Assert: Task can be failed runtime.OnEndInvoke( runtime.BeginInvokeCalls[1].AsyncHandle, /* succeeded: */ false, - "This is a test exception"); + new JSAsyncCallResult(jsonDocument, jsonDocument.RootElement)); Assert.False(unrelatedTask.IsCompleted); Assert.True(task.IsCompleted); @@ -106,20 +109,21 @@ namespace Microsoft.JSInterop.Tests { // Arrange var runtime = new TestJSRuntime(); + JSRuntime.SetCurrentJSRuntime(runtime); var obj1 = new object(); var obj2 = new object(); var obj3 = new object(); // Act // Showing we can pass the DotNetObject either as top-level args or nested - var obj1Ref = new DotNetObjectRef(obj1); - var obj1DifferentRef = new DotNetObjectRef(obj1); + var obj1Ref = DotNetObjectRef.Create(obj1); + var obj1DifferentRef = DotNetObjectRef.Create(obj1); runtime.InvokeAsync("test identifier", obj1Ref, new Dictionary { - { "obj2", new DotNetObjectRef(obj2) }, - { "obj3", new DotNetObjectRef(obj3) }, + { "obj2", DotNetObjectRef.Create(obj2) }, + { "obj3", DotNetObjectRef.Create(obj3) }, { "obj1SameRef", obj1Ref }, { "obj1DifferentRef", obj1DifferentRef }, }); @@ -127,28 +131,13 @@ namespace Microsoft.JSInterop.Tests // Assert: Serialized as expected var call = runtime.BeginInvokeCalls.Single(); Assert.Equal("test identifier", call.Identifier); - Assert.Equal("[\"__dotNetObject:1\",{\"obj2\":\"__dotNetObject:2\",\"obj3\":\"__dotNetObject:3\",\"obj1SameRef\":\"__dotNetObject:1\",\"obj1DifferentRef\":\"__dotNetObject:4\"}]", call.ArgsJson); + Assert.Equal("[{\"__dotNetObject\":1},{\"obj2\":{\"__dotNetObject\":3},\"obj3\":{\"__dotNetObject\":4},\"obj1SameRef\":{\"__dotNetObject\":1},\"obj1DifferentRef\":{\"__dotNetObject\":2}}]", call.ArgsJson); // Assert: Objects were tracked - Assert.Same(obj1, runtime.ArgSerializerStrategy.FindDotNetObject(1)); - Assert.Same(obj2, runtime.ArgSerializerStrategy.FindDotNetObject(2)); - Assert.Same(obj3, runtime.ArgSerializerStrategy.FindDotNetObject(3)); - Assert.Same(obj1, runtime.ArgSerializerStrategy.FindDotNetObject(4)); - } - - [Fact] - public void SupportsCustomSerializationForArguments() - { - // Arrange - var runtime = new TestJSRuntime(); - - // Arrange/Act - runtime.InvokeAsync("test identifier", - new WithCustomArgSerializer()); - - // Asssert - var call = runtime.BeginInvokeCalls.Single(); - Assert.Equal("[{\"key1\":\"value1\",\"key2\":123}]", call.ArgsJson); + Assert.Same(obj1, runtime.ObjectRefManager.FindDotNetObject(1)); + Assert.Same(obj1, runtime.ObjectRefManager.FindDotNetObject(2)); + Assert.Same(obj2, runtime.ObjectRefManager.FindDotNetObject(3)); + Assert.Same(obj3, runtime.ObjectRefManager.FindDotNetObject(4)); } class TestJSRuntime : JSRuntimeBase @@ -172,20 +161,8 @@ namespace Microsoft.JSInterop.Tests }); } - public void OnEndInvoke(long asyncHandle, bool succeeded, object resultOrException) - => EndInvokeJS(asyncHandle, succeeded, resultOrException); - } - - class WithCustomArgSerializer : ICustomArgSerializer - { - public object ToJsonPrimitive() - { - return new Dictionary - { - { "key1", "value1" }, - { "key2", 123 }, - }; - } + public void OnEndInvoke(long asyncHandle, bool succeeded, JSAsyncCallResult callResult) + => EndInvokeJS(asyncHandle, succeeded, callResult); } } } diff --git a/src/JSInterop/Microsoft.JSInterop/test/JSRuntimeTest.cs b/src/JSInterop/Microsoft.JSInterop/test/JSRuntimeTest.cs index b8a1c363dc..ca8c96df60 100644 --- a/src/JSInterop/Microsoft.JSInterop/test/JSRuntimeTest.cs +++ b/src/JSInterop/Microsoft.JSInterop/test/JSRuntimeTest.cs @@ -29,9 +29,6 @@ namespace Microsoft.JSInterop.Tests { public Task InvokeAsync(string identifier, params object[] args) => throw new NotImplementedException(); - - public void UntrackObjectRef(DotNetObjectRef dotNetObjectRef) - => throw new NotImplementedException(); } } } diff --git a/src/JSInterop/Microsoft.JSInterop/test/JsonUtilTest.cs b/src/JSInterop/Microsoft.JSInterop/test/JsonUtilTest.cs deleted file mode 100644 index 2b239faab9..0000000000 --- a/src/JSInterop/Microsoft.JSInterop/test/JsonUtilTest.cs +++ /dev/null @@ -1,349 +0,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 Microsoft.JSInterop.Internal; -using System; -using System.Collections.Generic; -using Xunit; - -namespace Microsoft.JSInterop.Tests -{ - public class JsonUtilTest - { - // It's not useful to have a complete set of behavior specifications for - // what the JSON serializer/deserializer does in all cases here. We merely - // expose a simple wrapper over a third-party library that maintains its - // own specs and tests. - // - // We should only add tests here to cover behaviors that Blazor itself - // depends on. - - [Theory] - [InlineData(null, "null")] - [InlineData("My string", "\"My string\"")] - [InlineData(123, "123")] - [InlineData(123.456f, "123.456")] - [InlineData(123.456d, "123.456")] - [InlineData(true, "true")] - public void CanSerializePrimitivesToJson(object value, string expectedJson) - { - Assert.Equal(expectedJson, Json.Serialize(value)); - } - - [Theory] - [InlineData("null", null)] - [InlineData("\"My string\"", "My string")] - [InlineData("123", 123L)] // Would also accept 123 as a System.Int32, but Int64 is fine as a default - [InlineData("123.456", 123.456d)] - [InlineData("true", true)] - public void CanDeserializePrimitivesFromJson(string json, object expectedValue) - { - Assert.Equal(expectedValue, Json.Deserialize(json)); - } - - [Fact] - public void CanSerializeClassToJson() - { - // Arrange - var person = new Person - { - Id = 1844, - Name = "Athos", - Pets = new[] { "Aramis", "Porthos", "D'Artagnan" }, - Hobby = Hobbies.Swordfighting, - SecondaryHobby = Hobbies.Reading, - Nicknames = new List { "Comte de la Fère", "Armand" }, - BirthInstant = new DateTimeOffset(1825, 8, 6, 18, 45, 21, TimeSpan.FromHours(-6)), - Age = new TimeSpan(7665, 1, 30, 0), - Allergies = new Dictionary { { "Ducks", true }, { "Geese", false } }, - }; - - // Act/Assert - Assert.Equal( - "{\"id\":1844,\"name\":\"Athos\",\"pets\":[\"Aramis\",\"Porthos\",\"D'Artagnan\"],\"hobby\":2,\"secondaryHobby\":1,\"nullHobby\":null,\"nicknames\":[\"Comte de la Fère\",\"Armand\"],\"birthInstant\":\"1825-08-06T18:45:21.0000000-06:00\",\"age\":\"7665.01:30:00\",\"allergies\":{\"Ducks\":true,\"Geese\":false}}", - Json.Serialize(person)); - } - - [Fact] - public void CanDeserializeClassFromJson() - { - // Arrange - var json = "{\"id\":1844,\"name\":\"Athos\",\"pets\":[\"Aramis\",\"Porthos\",\"D'Artagnan\"],\"hobby\":2,\"secondaryHobby\":1,\"nullHobby\":null,\"nicknames\":[\"Comte de la Fère\",\"Armand\"],\"birthInstant\":\"1825-08-06T18:45:21.0000000-06:00\",\"age\":\"7665.01:30:00\",\"allergies\":{\"Ducks\":true,\"Geese\":false}}"; - - // Act - var person = Json.Deserialize(json); - - // Assert - Assert.Equal(1844, person.Id); - Assert.Equal("Athos", person.Name); - Assert.Equal(new[] { "Aramis", "Porthos", "D'Artagnan" }, person.Pets); - Assert.Equal(Hobbies.Swordfighting, person.Hobby); - Assert.Equal(Hobbies.Reading, person.SecondaryHobby); - Assert.Null(person.NullHobby); - Assert.Equal(new[] { "Comte de la Fère", "Armand" }, person.Nicknames); - Assert.Equal(new DateTimeOffset(1825, 8, 6, 18, 45, 21, TimeSpan.FromHours(-6)), person.BirthInstant); - Assert.Equal(new TimeSpan(7665, 1, 30, 0), person.Age); - Assert.Equal(new Dictionary { { "Ducks", true }, { "Geese", false } }, person.Allergies); - } - - [Fact] - public void CanDeserializeWithCaseInsensitiveKeys() - { - // Arrange - var json = "{\"ID\":1844,\"NamE\":\"Athos\"}"; - - // Act - var person = Json.Deserialize(json); - - // Assert - Assert.Equal(1844, person.Id); - Assert.Equal("Athos", person.Name); - } - - [Fact] - public void DeserializationPrefersPropertiesOverFields() - { - // Arrange - var json = "{\"member1\":\"Hello\"}"; - - // Act - var person = Json.Deserialize(json); - - // Assert - Assert.Equal("Hello", person.Member1); - Assert.Null(person.member1); - } - - [Fact] - public void CanSerializeStructToJson() - { - // Arrange - var commandResult = new SimpleStruct - { - StringProperty = "Test", - BoolProperty = true, - NullableIntProperty = 1 - }; - - // Act - var result = Json.Serialize(commandResult); - - // Assert - Assert.Equal("{\"stringProperty\":\"Test\",\"boolProperty\":true,\"nullableIntProperty\":1}", result); - } - - [Fact] - public void CanDeserializeStructFromJson() - { - // Arrange - var json = "{\"stringProperty\":\"Test\",\"boolProperty\":true,\"nullableIntProperty\":1}"; - - //Act - var simpleError = Json.Deserialize(json); - - // Assert - Assert.Equal("Test", simpleError.StringProperty); - Assert.True(simpleError.BoolProperty); - Assert.Equal(1, simpleError.NullableIntProperty); - } - - [Fact] - public void CanCreateInstanceOfClassWithPrivateConstructor() - { - // Arrange - var expectedName = "NameValue"; - var json = $"{{\"Name\":\"{expectedName}\"}}"; - - // Act - var instance = Json.Deserialize(json); - - // Assert - Assert.Equal(expectedName, instance.Name); - } - - [Fact] - public void CanSetValueOfPublicPropertiesWithNonPublicSetters() - { - // Arrange - var expectedPrivateValue = "PrivateValue"; - var expectedProtectedValue = "ProtectedValue"; - var expectedInternalValue = "InternalValue"; - - var json = "{" + - $"\"PrivateSetter\":\"{expectedPrivateValue}\"," + - $"\"ProtectedSetter\":\"{expectedProtectedValue}\"," + - $"\"InternalSetter\":\"{expectedInternalValue}\"," + - "}"; - - // Act - var instance = Json.Deserialize(json); - - // Assert - Assert.Equal(expectedPrivateValue, instance.PrivateSetter); - Assert.Equal(expectedProtectedValue, instance.ProtectedSetter); - Assert.Equal(expectedInternalValue, instance.InternalSetter); - } - - [Fact] - public void RejectsTypesWithAmbiguouslyNamedProperties() - { - var ex = Assert.Throws(() => - { - Json.Deserialize("{}"); - }); - - Assert.Equal($"The type '{typeof(ClashingProperties).FullName}' contains multiple public properties " + - $"with names case-insensitively matching '{nameof(ClashingProperties.PROP1).ToLowerInvariant()}'. " + - $"Such types cannot be used for JSON deserialization.", - ex.Message); - } - - [Fact] - public void RejectsTypesWithAmbiguouslyNamedFields() - { - var ex = Assert.Throws(() => - { - Json.Deserialize("{}"); - }); - - Assert.Equal($"The type '{typeof(ClashingFields).FullName}' contains multiple public fields " + - $"with names case-insensitively matching '{nameof(ClashingFields.Field1).ToLowerInvariant()}'. " + - $"Such types cannot be used for JSON deserialization.", - ex.Message); - } - - [Fact] - public void NonEmptyConstructorThrowsUsefulException() - { - // Arrange - var json = "{\"Property\":1}"; - var type = typeof(NonEmptyConstructorPoco); - - // Act - var exception = Assert.Throws(() => - { - Json.Deserialize(json); - }); - - // Assert - Assert.Equal( - $"Cannot deserialize JSON into type '{type.FullName}' because it does not have a public parameterless constructor.", - exception.Message); - } - - // Test cases based on https://github.com/JamesNK/Newtonsoft.Json/blob/122afba9908832bd5ac207164ee6c303bfd65cf1/Src/Newtonsoft.Json.Tests/Utilities/StringUtilsTests.cs#L41 - // The only difference is that our logic doesn't have to handle space-separated words, - // because we're only use this for camelcasing .NET member names - // - // Not all of the following cases are really valid .NET member names, but we have no reason - // to implement more logic to detect invalid member names besides the basics (null or empty). - [Theory] - [InlineData("URLValue", "urlValue")] - [InlineData("URL", "url")] - [InlineData("ID", "id")] - [InlineData("I", "i")] - [InlineData("Person", "person")] - [InlineData("xPhone", "xPhone")] - [InlineData("XPhone", "xPhone")] - [InlineData("X_Phone", "x_Phone")] - [InlineData("X__Phone", "x__Phone")] - [InlineData("IsCIA", "isCIA")] - [InlineData("VmQ", "vmQ")] - [InlineData("Xml2Json", "xml2Json")] - [InlineData("SnAkEcAsE", "snAkEcAsE")] - [InlineData("SnA__kEcAsE", "snA__kEcAsE")] - [InlineData("already_snake_case_", "already_snake_case_")] - [InlineData("IsJSONProperty", "isJSONProperty")] - [InlineData("SHOUTING_CASE", "shoutinG_CASE")] - [InlineData("9999-12-31T23:59:59.9999999Z", "9999-12-31T23:59:59.9999999Z")] - [InlineData("Hi!! This is text. Time to test.", "hi!! This is text. Time to test.")] - [InlineData("BUILDING", "building")] - [InlineData("BUILDINGProperty", "buildingProperty")] - public void MemberNameToCamelCase_Valid(string input, string expectedOutput) - { - Assert.Equal(expectedOutput, CamelCase.MemberNameToCamelCase(input)); - } - - [Theory] - [InlineData("")] - [InlineData(null)] - public void MemberNameToCamelCase_Invalid(string input) - { - var ex = Assert.Throws(() => - CamelCase.MemberNameToCamelCase(input)); - Assert.Equal("value", ex.ParamName); - Assert.StartsWith($"The value '{input ?? "null"}' is not a valid member name.", ex.Message); - } - - class NonEmptyConstructorPoco - { - public NonEmptyConstructorPoco(int parameter) { } - - public int Property { get; set; } - } - - struct SimpleStruct - { - public string StringProperty { get; set; } - public bool BoolProperty { get; set; } - public int? NullableIntProperty { get; set; } - } - - class Person - { - public int Id { get; set; } - public string Name { get; set; } - public string[] Pets { get; set; } - public Hobbies Hobby { get; set; } - public Hobbies? SecondaryHobby { get; set; } - public Hobbies? NullHobby { get; set; } - public IList Nicknames { get; set; } - public DateTimeOffset BirthInstant { get; set; } - public TimeSpan Age { get; set; } - public IDictionary Allergies { get; set; } - } - - enum Hobbies { Reading = 1, Swordfighting = 2 } - -#pragma warning disable 0649 - class ClashingProperties - { - public string Prop1 { get; set; } - public int PROP1 { get; set; } - } - - class ClashingFields - { - public string Field1; - public int field1; - } - - class PrefersPropertiesOverFields - { - public string member1; - public string Member1 { get; set; } - } -#pragma warning restore 0649 - - class PrivateConstructor - { - public string Name { get; set; } - - private PrivateConstructor() - { - } - - public PrivateConstructor(string name) - { - Name = name; - } - } - - class NonPublicSetterOnPublicProperty - { - public string PrivateSetter { get; private set; } - public string ProtectedSetter { get; protected set; } - public string InternalSetter { get; internal set; } - } - } -} From 73e95c56c18b45d0c513282a549e953f93b5f9c9 Mon Sep 17 00:00:00 2001 From: John Luo Date: Wed, 29 May 2019 16:22:17 -0700 Subject: [PATCH 0099/1101] Allow flaky attribute on class and assembly (dotnet/extensions#1742) * Allow flaky attribute on class and assembly\n\nCommit migrated from https://github.com/dotnet/extensions/commit/23ba460e278c3335f70595ebc5b19fe42e80da51 --- src/ObjectPool/test/DefaultObjectPoolTest.cs | 2 +- src/Testing/src/xunit/FlakyAttribute.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/ObjectPool/test/DefaultObjectPoolTest.cs b/src/ObjectPool/test/DefaultObjectPoolTest.cs index b44aa7e1c7..9bf84dca03 100644 --- a/src/ObjectPool/test/DefaultObjectPoolTest.cs +++ b/src/ObjectPool/test/DefaultObjectPoolTest.cs @@ -45,7 +45,7 @@ namespace Microsoft.Extensions.ObjectPool { // Arrange var pool = new DefaultObjectPool>(new ListPolicy()); - + // Act var list = pool.Get(); diff --git a/src/Testing/src/xunit/FlakyAttribute.cs b/src/Testing/src/xunit/FlakyAttribute.cs index f58026c7ca..ab4450e685 100644 --- a/src/Testing/src/xunit/FlakyAttribute.cs +++ b/src/Testing/src/xunit/FlakyAttribute.cs @@ -50,7 +50,7 @@ namespace Microsoft.AspNetCore.Testing.xunit /// /// [TraitDiscoverer("Microsoft.AspNetCore.Testing.xunit.FlakyTestDiscoverer", "Microsoft.AspNetCore.Testing")] - [AttributeUsage(AttributeTargets.Method)] + [AttributeUsage(AttributeTargets.Method | AttributeTargets.Class | AttributeTargets.Assembly)] public sealed class FlakyAttribute : Attribute, ITraitAttribute { /// From f402cc566a3fd982999f8a4e157632488865e118 Mon Sep 17 00:00:00 2001 From: Yves Marx Date: Wed, 5 Jun 2019 08:19:46 +0200 Subject: [PATCH 0100/1101] ActivatorUtilities.CreateInstance: allocation optimization \n\nCommit migrated from https://github.com/dotnet/extensions/commit/689a832cedd7eec1c9643728551b77233f18b08d --- .../ActivatorUtilities/ActivatorUtilities.cs | 51 +++++++++---------- 1 file changed, 25 insertions(+), 26 deletions(-) diff --git a/src/Shared/ActivatorUtilities/ActivatorUtilities.cs b/src/Shared/ActivatorUtilities/ActivatorUtilities.cs index c88914ee28..3fd2b557ff 100644 --- a/src/Shared/ActivatorUtilities/ActivatorUtilities.cs +++ b/src/Shared/ActivatorUtilities/ActivatorUtilities.cs @@ -43,43 +43,45 @@ namespace Microsoft.Extensions.Internal int bestLength = -1; var seenPreferred = false; - ConstructorMatcher bestMatcher = null; + ConstructorMatcher bestMatcher = default; if (!instanceType.GetTypeInfo().IsAbstract) { foreach (var constructor in instanceType .GetTypeInfo() - .DeclaredConstructors - .Where(c => !c.IsStatic && c.IsPublic)) + .DeclaredConstructors) { - var matcher = new ConstructorMatcher(constructor); - var isPreferred = constructor.IsDefined(typeof(ActivatorUtilitiesConstructorAttribute), false); - var length = matcher.Match(parameters); - - if (isPreferred) + if (!constructor.IsStatic && constructor.IsPublic) { - if (seenPreferred) + var matcher = new ConstructorMatcher(constructor); + var isPreferred = constructor.IsDefined(typeof(ActivatorUtilitiesConstructorAttribute), false); + var length = matcher.Match(parameters); + + if (isPreferred) { - ThrowMultipleCtorsMarkedWithAttributeException(); + if (seenPreferred) + { + ThrowMultipleCtorsMarkedWithAttributeException(); + } + + if (length == -1) + { + ThrowMarkedCtorDoesNotTakeAllProvidedArguments(); + } } - if (length == -1) + if (isPreferred || bestLength < length) { - ThrowMarkedCtorDoesNotTakeAllProvidedArguments(); + bestLength = length; + bestMatcher = matcher; } - } - if (isPreferred || bestLength < length) - { - bestLength = length; - bestMatcher = matcher; + seenPreferred |= isPreferred; } - - seenPreferred |= isPreferred; } } - if (bestMatcher == null) + if (bestLength == -1) { var message = $"A suitable constructor for type '{instanceType}' could not be located. Ensure the type is concrete and services are registered for all parameters of a public constructor."; throw new InvalidOperationException(message); @@ -327,18 +329,16 @@ namespace Microsoft.Extensions.Internal return true; } - private class ConstructorMatcher + private struct ConstructorMatcher { private readonly ConstructorInfo _constructor; private readonly ParameterInfo[] _parameters; private readonly object[] _parameterValues; - private readonly bool[] _parameterValuesSet; public ConstructorMatcher(ConstructorInfo constructor) { _constructor = constructor; _parameters = _constructor.GetParameters(); - _parameterValuesSet = new bool[_parameters.Length]; _parameterValues = new object[_parameters.Length]; } @@ -353,11 +353,10 @@ namespace Microsoft.Extensions.Internal for (var applyIndex = applyIndexStart; givenMatched == false && applyIndex != _parameters.Length; ++applyIndex) { - if (_parameterValuesSet[applyIndex] == false && + if (_parameterValues[applyIndex] == null && _parameters[applyIndex].ParameterType.GetTypeInfo().IsAssignableFrom(givenType)) { givenMatched = true; - _parameterValuesSet[applyIndex] = true; _parameterValues[applyIndex] = givenParameters[givenIndex]; if (applyIndexStart == applyIndex) { @@ -382,7 +381,7 @@ namespace Microsoft.Extensions.Internal { for (var index = 0; index != _parameters.Length; index++) { - if (_parameterValuesSet[index] == false) + if (_parameterValues[index] == null) { var value = provider.GetService(_parameters[index].ParameterType); if (value == null) From 3875b0f292d6db6b7d5a51366449004ac0caa713 Mon Sep 17 00:00:00 2001 From: Justin Kotalik Date: Fri, 7 Jun 2019 11:32:18 -0700 Subject: [PATCH 0101/1101] Add retries to HttpClientSlim on macOS. (dotnet/extensions#1807) \n\nCommit migrated from https://github.com/dotnet/extensions/commit/d4d110a8381de00d8754b8efd2c21012263f73a5 --- src/Testing/src/HttpClientSlim.cs | 74 ++++++++++++++++++++++--------- 1 file changed, 54 insertions(+), 20 deletions(-) diff --git a/src/Testing/src/HttpClientSlim.cs b/src/Testing/src/HttpClientSlim.cs index 6214ffefc1..890ec2d160 100644 --- a/src/Testing/src/HttpClientSlim.cs +++ b/src/Testing/src/HttpClientSlim.cs @@ -8,6 +8,7 @@ using System.Net; using System.Net.Http; using System.Net.Security; using System.Net.Sockets; +using System.Runtime.InteropServices; using System.Security.Authentication; using System.Text; using System.Threading.Tasks; @@ -24,17 +25,20 @@ namespace Microsoft.AspNetCore.Testing public static async Task GetStringAsync(Uri requestUri, bool validateCertificate = true) { - using (var stream = await GetStream(requestUri, validateCertificate).ConfigureAwait(false)) + return await RetryRequest(async () => { - using (var writer = new StreamWriter(stream, Encoding.ASCII, bufferSize: 1024, leaveOpen: true)) + using (var stream = await GetStream(requestUri, validateCertificate).ConfigureAwait(false)) { - await writer.WriteAsync($"GET {requestUri.PathAndQuery} HTTP/1.0\r\n").ConfigureAwait(false); - await writer.WriteAsync($"Host: {GetHost(requestUri)}\r\n").ConfigureAwait(false); - await writer.WriteAsync("\r\n").ConfigureAwait(false); - } + using (var writer = new StreamWriter(stream, Encoding.ASCII, bufferSize: 1024, leaveOpen: true)) + { + await writer.WriteAsync($"GET {requestUri.PathAndQuery} HTTP/1.0\r\n").ConfigureAwait(false); + await writer.WriteAsync($"Host: {GetHost(requestUri)}\r\n").ConfigureAwait(false); + await writer.WriteAsync("\r\n").ConfigureAwait(false); + } - return await ReadResponse(stream).ConfigureAwait(false); - } + return await ReadResponse(stream).ConfigureAwait(false); + } + }); } internal static string GetHost(Uri requestUri) @@ -62,21 +66,24 @@ namespace Microsoft.AspNetCore.Testing public static async Task PostAsync(Uri requestUri, HttpContent content, bool validateCertificate = true) { - using (var stream = await GetStream(requestUri, validateCertificate)) + return await RetryRequest(async () => { - using (var writer = new StreamWriter(stream, Encoding.ASCII, bufferSize: 1024, leaveOpen: true)) + using (var stream = await GetStream(requestUri, validateCertificate)) { - await writer.WriteAsync($"POST {requestUri.PathAndQuery} HTTP/1.0\r\n").ConfigureAwait(false); - await writer.WriteAsync($"Host: {requestUri.Authority}\r\n").ConfigureAwait(false); - await writer.WriteAsync($"Content-Type: {content.Headers.ContentType}\r\n").ConfigureAwait(false); - await writer.WriteAsync($"Content-Length: {content.Headers.ContentLength}\r\n").ConfigureAwait(false); - await writer.WriteAsync("\r\n").ConfigureAwait(false); + using (var writer = new StreamWriter(stream, Encoding.ASCII, bufferSize: 1024, leaveOpen: true)) + { + await writer.WriteAsync($"POST {requestUri.PathAndQuery} HTTP/1.0\r\n").ConfigureAwait(false); + await writer.WriteAsync($"Host: {requestUri.Authority}\r\n").ConfigureAwait(false); + await writer.WriteAsync($"Content-Type: {content.Headers.ContentType}\r\n").ConfigureAwait(false); + await writer.WriteAsync($"Content-Length: {content.Headers.ContentLength}\r\n").ConfigureAwait(false); + await writer.WriteAsync("\r\n").ConfigureAwait(false); + } + + await content.CopyToAsync(stream).ConfigureAwait(false); + + return await ReadResponse(stream).ConfigureAwait(false); } - - await content.CopyToAsync(stream).ConfigureAwait(false); - - return await ReadResponse(stream).ConfigureAwait(false); - } + }); } private static async Task ReadResponse(Stream stream) @@ -94,6 +101,33 @@ namespace Microsoft.AspNetCore.Testing } } + private static async Task RetryRequest(Func> retryBlock) + { + var retryCount = 1; + if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX)) + { + retryCount = 3; + } + + for (var retry = 0; retry < retryCount; retry++) + { + try + { + return await retryBlock().ConfigureAwait(false); + } + catch (InvalidDataException) + { + if (retry == retryCount - 1) + { + throw; + } + } + } + + // This will never be hit. + throw new NotSupportedException(); + } + private static HttpStatusCode GetStatus(string response) { var statusStart = response.IndexOf(' ') + 1; From e31f189c71ad5940147934a991418d75ef2d80bc Mon Sep 17 00:00:00 2001 From: "dotnet-maestro[bot]" Date: Fri, 7 Jun 2019 19:08:51 +0000 Subject: [PATCH 0102/1101] [master] Update dependencies from dotnet/core-setup (dotnet/extensions#1803) * Update dependencies from https://github.com/dotnet/core-setup build 20190605.02 - Microsoft.NETCore.App - 3.0.0-preview7-27805-02 - NETStandard.Library.Ref - 2.1.0-preview7-27805-02 Dependency coherency updates - Microsoft.Win32.Registry - 4.6.0-preview7.19305.9 (parent: Microsoft.NETCore.App) - System.ComponentModel.Annotations - 4.6.0-preview7.19305.9 (parent: Microsoft.NETCore.App) - System.Data.SqlClient - 4.7.0-preview7.19305.9 (parent: Microsoft.NETCore.App) - System.Diagnostics.DiagnosticSource - 4.6.0-preview7.19305.9 (parent: Microsoft.NETCore.App) - System.Diagnostics.EventLog - 4.6.0-preview7.19305.9 (parent: Microsoft.NETCore.App) - System.IO.Pipelines - 4.6.0-preview7.19305.9 (parent: Microsoft.NETCore.App) - System.Reflection.Metadata - 1.7.0-preview7.19305.9 (parent: Microsoft.NETCore.App) - System.Runtime.CompilerServices.Unsafe - 4.6.0-preview7.19305.9 (parent: Microsoft.NETCore.App) - System.Security.Cryptography.Cng - 4.6.0-preview7.19305.9 (parent: Microsoft.NETCore.App) - System.Security.Cryptography.Xml - 4.6.0-preview7.19305.9 (parent: Microsoft.NETCore.App) - System.ServiceProcess.ServiceController - 4.6.0-preview7.19305.9 (parent: Microsoft.NETCore.App) - System.Text.Encodings.Web - 4.6.0-preview7.19305.9 (parent: Microsoft.NETCore.App) - System.Text.Json - 4.6.0-preview7.19305.9 (parent: Microsoft.NETCore.App) - Microsoft.NETCore.Platforms - 3.0.0-preview7.19305.9 (parent: Microsoft.NETCore.App) * Update dependencies from https://github.com/dotnet/core-setup build 20190523.07 - Microsoft.NETCore.App - 3.0.0-preview6-27723-07 - NETStandard.Library.Ref - 2.1.0-preview6-27723-07 Dependency coherency updates - Microsoft.Win32.Registry - 4.6.0-preview6.19273.4 (parent: Microsoft.NETCore.App) - System.ComponentModel.Annotations - 4.6.0-preview6.19273.4 (parent: Microsoft.NETCore.App) - System.Data.SqlClient - 4.7.0-preview6.19273.4 (parent: Microsoft.NETCore.App) - System.Diagnostics.DiagnosticSource - 4.6.0-preview6.19273.4 (parent: Microsoft.NETCore.App) - System.Diagnostics.EventLog - 4.6.0-preview6.19273.4 (parent: Microsoft.NETCore.App) - System.IO.Pipelines - 4.6.0-preview6.19273.4 (parent: Microsoft.NETCore.App) - System.Reflection.Metadata - 1.7.0-preview6.19273.4 (parent: Microsoft.NETCore.App) - System.Runtime.CompilerServices.Unsafe - 4.6.0-preview6.19273.4 (parent: Microsoft.NETCore.App) - System.Security.Cryptography.Cng - 4.6.0-preview6.19273.4 (parent: Microsoft.NETCore.App) - System.Security.Cryptography.Xml - 4.6.0-preview6.19273.4 (parent: Microsoft.NETCore.App) - System.ServiceProcess.ServiceController - 4.6.0-preview6.19273.4 (parent: Microsoft.NETCore.App) - System.Text.Encodings.Web - 4.6.0-preview6.19273.4 (parent: Microsoft.NETCore.App) - System.Text.Json - 4.6.0-preview6.19273.4 (parent: Microsoft.NETCore.App) - Microsoft.NETCore.Platforms - 3.0.0-preview6.19273.4 (parent: Microsoft.NETCore.App) * Update dependencies from https://github.com/dotnet/core-setup build 20190606.03 - Microsoft.NETCore.App - 3.0.0-preview7-27806-03 - NETStandard.Library.Ref - 2.1.0-preview7-27806-03 Dependency coherency updates - Microsoft.Win32.Registry - 4.6.0-preview7.19306.6 (parent: Microsoft.NETCore.App) - System.ComponentModel.Annotations - 4.6.0-preview7.19306.6 (parent: Microsoft.NETCore.App) - System.Data.SqlClient - 4.7.0-preview7.19306.6 (parent: Microsoft.NETCore.App) - System.Diagnostics.DiagnosticSource - 4.6.0-preview7.19306.6 (parent: Microsoft.NETCore.App) - System.Diagnostics.EventLog - 4.6.0-preview7.19306.6 (parent: Microsoft.NETCore.App) - System.IO.Pipelines - 4.6.0-preview7.19306.6 (parent: Microsoft.NETCore.App) - System.Reflection.Metadata - 1.7.0-preview7.19306.6 (parent: Microsoft.NETCore.App) - System.Runtime.CompilerServices.Unsafe - 4.6.0-preview7.19306.6 (parent: Microsoft.NETCore.App) - System.Security.Cryptography.Cng - 4.6.0-preview7.19306.6 (parent: Microsoft.NETCore.App) - System.Security.Cryptography.Xml - 4.6.0-preview7.19306.6 (parent: Microsoft.NETCore.App) - System.ServiceProcess.ServiceController - 4.6.0-preview7.19306.6 (parent: Microsoft.NETCore.App) - System.Text.Encodings.Web - 4.6.0-preview7.19306.6 (parent: Microsoft.NETCore.App) - System.Text.Json - 4.6.0-preview7.19306.6 (parent: Microsoft.NETCore.App) - Microsoft.NETCore.Platforms - 3.0.0-preview7.19306.6 (parent: Microsoft.NETCore.App) * namespace \n\nCommit migrated from https://github.com/dotnet/extensions/commit/537f0c2db7f52b50f7589d0c6db026d50d856863 --- src/JSInterop/Microsoft.JSInterop/src/JSInProcessRuntimeBase.cs | 2 +- src/JSInterop/Microsoft.JSInterop/src/JSRuntimeBase.cs | 2 +- .../Microsoft.JSInterop/src/JsonSerializerOptionsProvider.cs | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/JSInterop/Microsoft.JSInterop/src/JSInProcessRuntimeBase.cs b/src/JSInterop/Microsoft.JSInterop/src/JSInProcessRuntimeBase.cs index 7e6dcdd462..2565496a54 100644 --- a/src/JSInterop/Microsoft.JSInterop/src/JSInProcessRuntimeBase.cs +++ b/src/JSInterop/Microsoft.JSInterop/src/JSInProcessRuntimeBase.cs @@ -1,7 +1,7 @@ // 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.Text.Json.Serialization; +using System.Text.Json; namespace Microsoft.JSInterop { diff --git a/src/JSInterop/Microsoft.JSInterop/src/JSRuntimeBase.cs b/src/JSInterop/Microsoft.JSInterop/src/JSRuntimeBase.cs index 70ab856a89..3e39e42e9a 100644 --- a/src/JSInterop/Microsoft.JSInterop/src/JSRuntimeBase.cs +++ b/src/JSInterop/Microsoft.JSInterop/src/JSRuntimeBase.cs @@ -4,7 +4,7 @@ using System; using System.Collections.Concurrent; using System.Runtime.ExceptionServices; -using System.Text.Json.Serialization; +using System.Text.Json; using System.Threading; using System.Threading.Tasks; using Microsoft.JSInterop.Internal; diff --git a/src/JSInterop/Microsoft.JSInterop/src/JsonSerializerOptionsProvider.cs b/src/JSInterop/Microsoft.JSInterop/src/JsonSerializerOptionsProvider.cs index 0292039eaf..fb299aaf02 100644 --- a/src/JSInterop/Microsoft.JSInterop/src/JsonSerializerOptionsProvider.cs +++ b/src/JSInterop/Microsoft.JSInterop/src/JsonSerializerOptionsProvider.cs @@ -1,7 +1,7 @@ // 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.Text.Json.Serialization; +using System.Text.Json; namespace Microsoft.JSInterop { From 5868b659346509abbe58ab631d644e9381f9b7ae Mon Sep 17 00:00:00 2001 From: Nate McMaster Date: Wed, 12 Jun 2019 22:39:41 -0700 Subject: [PATCH 0103/1101] Use even more Arcade and other csproj cleanups (dotnet/extensions#1833) * Use Arcade's convention for setting IsPackable (must be explicitly set) * Use Arcade conventions for using DebugType and eng/Versions.props * Remove dead code * Update restore feeds in daily builds.md * Disable UsingToolNetFrameworkReferenceAssemblies in analyzer tests * Remove usage of TestGroupName (an obsolete KoreBuild setting) * Use IVT as a .csproj attribute \n\nCommit migrated from https://github.com/dotnet/extensions/commit/f12d709976e382672ce100bc00381a8847f06489 --- ...rosoft.Extensions.Configuration.KeyPerFile.csproj | 1 + ...icrosoft.Extensions.FileProviders.Embedded.csproj | 4 ++-- ...ions.Diagnostics.HealthChecks.Abstractions.csproj | 1 + ...rosoft.Extensions.Diagnostics.HealthChecks.csproj | 1 + .../src/Microsoft.JSInterop.csproj | 3 ++- .../src/Mono.WebAssembly.Interop.csproj | 1 + ...osoft.Extensions.Localization.Abstractions.csproj | 1 + .../src/Microsoft.Extensions.Localization.csproj | 1 + .../src/Microsoft.Extensions.ObjectPool.csproj | 5 +++++ src/ObjectPool/src/Properties/AssemblyInfo.cs | 3 --- src/Shared/ActivatorUtilities/sharedsources.props | 12 ------------ .../src/Microsoft.Extensions.WebEncoders.csproj | 1 + 12 files changed, 16 insertions(+), 18 deletions(-) delete mode 100644 src/ObjectPool/src/Properties/AssemblyInfo.cs delete mode 100644 src/Shared/ActivatorUtilities/sharedsources.props diff --git a/src/Configuration.KeyPerFile/src/Microsoft.Extensions.Configuration.KeyPerFile.csproj b/src/Configuration.KeyPerFile/src/Microsoft.Extensions.Configuration.KeyPerFile.csproj index 5bd7b2c7ef..8c76c174ad 100644 --- a/src/Configuration.KeyPerFile/src/Microsoft.Extensions.Configuration.KeyPerFile.csproj +++ b/src/Configuration.KeyPerFile/src/Microsoft.Extensions.Configuration.KeyPerFile.csproj @@ -3,6 +3,7 @@ Configuration provider that uses files in a directory for Microsoft.Extensions.Configuration. netstandard2.0 + true true diff --git a/src/FileProviders/Embedded/src/Microsoft.Extensions.FileProviders.Embedded.csproj b/src/FileProviders/Embedded/src/Microsoft.Extensions.FileProviders.Embedded.csproj index 7d84c19f9d..8119c26339 100644 --- a/src/FileProviders/Embedded/src/Microsoft.Extensions.FileProviders.Embedded.csproj +++ b/src/FileProviders/Embedded/src/Microsoft.Extensions.FileProviders.Embedded.csproj @@ -5,6 +5,7 @@ File provider for files in embedded resources for Microsoft.Extensions.FileProviders. netstandard2.0 $(MSBuildProjectName).nuspec + true true @@ -24,7 +25,6 @@ - $(PackageTags.Replace(';',' ')) <_OutputBinary>@(BuiltProjectOutputGroupOutput) <_OutputSymbol>@(DebugSymbolsProjectOutputGroupOutput) <_OutputDocumentation>@(DocumentationProjectOutputGroupOutput) @@ -37,7 +37,7 @@ - + diff --git a/src/HealthChecks/Abstractions/src/Microsoft.Extensions.Diagnostics.HealthChecks.Abstractions.csproj b/src/HealthChecks/Abstractions/src/Microsoft.Extensions.Diagnostics.HealthChecks.Abstractions.csproj index 2bba5959a3..306a303421 100644 --- a/src/HealthChecks/Abstractions/src/Microsoft.Extensions.Diagnostics.HealthChecks.Abstractions.csproj +++ b/src/HealthChecks/Abstractions/src/Microsoft.Extensions.Diagnostics.HealthChecks.Abstractions.csproj @@ -11,6 +11,7 @@ Microsoft.Extensions.Diagnostics.HealthChecks.IHealthCheck $(NoWarn);CS1591 true diagnostics;healthchecks + true true diff --git a/src/HealthChecks/HealthChecks/src/Microsoft.Extensions.Diagnostics.HealthChecks.csproj b/src/HealthChecks/HealthChecks/src/Microsoft.Extensions.Diagnostics.HealthChecks.csproj index 463e5b3632..9670e8bb7b 100644 --- a/src/HealthChecks/HealthChecks/src/Microsoft.Extensions.Diagnostics.HealthChecks.csproj +++ b/src/HealthChecks/HealthChecks/src/Microsoft.Extensions.Diagnostics.HealthChecks.csproj @@ -10,6 +10,7 @@ Microsoft.Extensions.Diagnostics.HealthChecks.IHealthChecksBuilder $(NoWarn);CS1591 true diagnostics;healthchecks + true true diff --git a/src/JSInterop/Microsoft.JSInterop/src/Microsoft.JSInterop.csproj b/src/JSInterop/Microsoft.JSInterop/src/Microsoft.JSInterop.csproj index bc912b97cc..8dfb1ef22e 100644 --- a/src/JSInterop/Microsoft.JSInterop/src/Microsoft.JSInterop.csproj +++ b/src/JSInterop/Microsoft.JSInterop/src/Microsoft.JSInterop.csproj @@ -5,13 +5,14 @@ Abstractions and features for interop between .NET and JavaScript code. javascript;interop true + true true - + diff --git a/src/JSInterop/Mono.WebAssembly.Interop/src/Mono.WebAssembly.Interop.csproj b/src/JSInterop/Mono.WebAssembly.Interop/src/Mono.WebAssembly.Interop.csproj index 413d084e48..fc92708456 100644 --- a/src/JSInterop/Mono.WebAssembly.Interop/src/Mono.WebAssembly.Interop.csproj +++ b/src/JSInterop/Mono.WebAssembly.Interop/src/Mono.WebAssembly.Interop.csproj @@ -5,6 +5,7 @@ Abstractions and features for interop between Mono WebAssembly and JavaScript code. wasm;javascript;interop true + true true diff --git a/src/Localization/Abstractions/src/Microsoft.Extensions.Localization.Abstractions.csproj b/src/Localization/Abstractions/src/Microsoft.Extensions.Localization.Abstractions.csproj index 33f58b6358..86af6dfde9 100644 --- a/src/Localization/Abstractions/src/Microsoft.Extensions.Localization.Abstractions.csproj +++ b/src/Localization/Abstractions/src/Microsoft.Extensions.Localization.Abstractions.csproj @@ -10,6 +10,7 @@ Microsoft.Extensions.Localization.IStringLocalizer<T> $(NoWarn);CS1591 true localization + true true diff --git a/src/Localization/Localization/src/Microsoft.Extensions.Localization.csproj b/src/Localization/Localization/src/Microsoft.Extensions.Localization.csproj index f16cbd9dab..5949cc5842 100644 --- a/src/Localization/Localization/src/Microsoft.Extensions.Localization.csproj +++ b/src/Localization/Localization/src/Microsoft.Extensions.Localization.csproj @@ -7,6 +7,7 @@ $(NoWarn);CS1591 true localization + true true diff --git a/src/ObjectPool/src/Microsoft.Extensions.ObjectPool.csproj b/src/ObjectPool/src/Microsoft.Extensions.ObjectPool.csproj index 71e9abed79..c193ad6e1f 100644 --- a/src/ObjectPool/src/Microsoft.Extensions.ObjectPool.csproj +++ b/src/ObjectPool/src/Microsoft.Extensions.ObjectPool.csproj @@ -6,7 +6,12 @@ $(NoWarn);CS1591 true pooling + true true + + + + diff --git a/src/ObjectPool/src/Properties/AssemblyInfo.cs b/src/ObjectPool/src/Properties/AssemblyInfo.cs deleted file mode 100644 index 121f8990b1..0000000000 --- a/src/ObjectPool/src/Properties/AssemblyInfo.cs +++ /dev/null @@ -1,3 +0,0 @@ -using System.Runtime.CompilerServices; - -[assembly: InternalsVisibleTo("Microsoft.Extensions.ObjectPool.Tests, PublicKey=0024000004800000940000000602000000240000525341310004000001000100f33a29044fa9d740c9b3213a93e57c84b472c84e0b8a0e1ae48e67a9f8f6de9d5f7f3d52ac23e48ac51801f1dc950abe901da34d2a9e3baadb141a17c77ef3c565dd5ee5054b91cf63bb3c6ab83f72ab3aafe93d0fc3c2348b764fafb0b1c0733de51459aeab46580384bf9d74c4e28164b7cde247f891ba07891c9d872ad2bb")] diff --git a/src/Shared/ActivatorUtilities/sharedsources.props b/src/Shared/ActivatorUtilities/sharedsources.props deleted file mode 100644 index f754677531..0000000000 --- a/src/Shared/ActivatorUtilities/sharedsources.props +++ /dev/null @@ -1,12 +0,0 @@ - - - false - - - - - true - $(ContentTargetFolders)\cs\netstandard1.0\ - - - diff --git a/src/WebEncoders/src/Microsoft.Extensions.WebEncoders.csproj b/src/WebEncoders/src/Microsoft.Extensions.WebEncoders.csproj index 8f60f8f983..cec4a31d86 100644 --- a/src/WebEncoders/src/Microsoft.Extensions.WebEncoders.csproj +++ b/src/WebEncoders/src/Microsoft.Extensions.WebEncoders.csproj @@ -6,6 +6,7 @@ $(NoWarn);CS1591 true aspnetcore + true true From 2dd8eb61baae0a90013409ddb429382f99e99265 Mon Sep 17 00:00:00 2001 From: Nate McMaster Date: Thu, 13 Jun 2019 16:27:19 -0700 Subject: [PATCH 0104/1101] Disable transitive project references in test projects (dotnet/extensions#1834) \n\nCommit migrated from https://github.com/dotnet/extensions/commit/e7d5bea3bbd9c942c523e9068b17f11bb35f332b --- ...rosoft.Extensions.Configuration.KeyPerFile.Tests.csproj | 6 ++++++ ...icrosoft.Extensions.FileProviders.Embedded.Tests.csproj | 2 ++ ...rosoft.Extensions.Diagnostics.HealthChecks.Tests.csproj | 7 +++++++ .../test/Microsoft.JSInterop.Tests.csproj | 2 +- .../test/Microsoft.Extensions.Localization.Tests.csproj | 7 +++++++ .../test/Microsoft.Extensions.ObjectPool.Tests.csproj | 2 +- src/Testing/test/Microsoft.AspNetCore.Testing.Tests.csproj | 5 +---- .../test/Microsoft.Extensions.WebEncoders.Tests.csproj | 3 ++- 8 files changed, 27 insertions(+), 7 deletions(-) diff --git a/src/Configuration.KeyPerFile/test/Microsoft.Extensions.Configuration.KeyPerFile.Tests.csproj b/src/Configuration.KeyPerFile/test/Microsoft.Extensions.Configuration.KeyPerFile.Tests.csproj index aee7e7a7bf..95a73ae6cf 100644 --- a/src/Configuration.KeyPerFile/test/Microsoft.Extensions.Configuration.KeyPerFile.Tests.csproj +++ b/src/Configuration.KeyPerFile/test/Microsoft.Extensions.Configuration.KeyPerFile.Tests.csproj @@ -9,7 +9,13 @@ + + + + + + diff --git a/src/FileProviders/Embedded/test/Microsoft.Extensions.FileProviders.Embedded.Tests.csproj b/src/FileProviders/Embedded/test/Microsoft.Extensions.FileProviders.Embedded.Tests.csproj index 8024fb44fd..8377747702 100644 --- a/src/FileProviders/Embedded/test/Microsoft.Extensions.FileProviders.Embedded.Tests.csproj +++ b/src/FileProviders/Embedded/test/Microsoft.Extensions.FileProviders.Embedded.Tests.csproj @@ -9,7 +9,9 @@ + + diff --git a/src/HealthChecks/HealthChecks/test/Microsoft.Extensions.Diagnostics.HealthChecks.Tests.csproj b/src/HealthChecks/HealthChecks/test/Microsoft.Extensions.Diagnostics.HealthChecks.Tests.csproj index 08cd6a35f1..21763e5165 100644 --- a/src/HealthChecks/HealthChecks/test/Microsoft.Extensions.Diagnostics.HealthChecks.Tests.csproj +++ b/src/HealthChecks/HealthChecks/test/Microsoft.Extensions.Diagnostics.HealthChecks.Tests.csproj @@ -8,8 +8,15 @@ + + + + + + + diff --git a/src/JSInterop/Microsoft.JSInterop/test/Microsoft.JSInterop.Tests.csproj b/src/JSInterop/Microsoft.JSInterop/test/Microsoft.JSInterop.Tests.csproj index a39b082180..34ac70b801 100644 --- a/src/JSInterop/Microsoft.JSInterop/test/Microsoft.JSInterop.Tests.csproj +++ b/src/JSInterop/Microsoft.JSInterop/test/Microsoft.JSInterop.Tests.csproj @@ -5,7 +5,7 @@ - + diff --git a/src/Localization/Localization/test/Microsoft.Extensions.Localization.Tests.csproj b/src/Localization/Localization/test/Microsoft.Extensions.Localization.Tests.csproj index 32c6263ac3..17254e2110 100644 --- a/src/Localization/Localization/test/Microsoft.Extensions.Localization.Tests.csproj +++ b/src/Localization/Localization/test/Microsoft.Extensions.Localization.Tests.csproj @@ -5,9 +5,16 @@ + + + + + + + diff --git a/src/ObjectPool/test/Microsoft.Extensions.ObjectPool.Tests.csproj b/src/ObjectPool/test/Microsoft.Extensions.ObjectPool.Tests.csproj index 8c14917a65..1f2ad67664 100644 --- a/src/ObjectPool/test/Microsoft.Extensions.ObjectPool.Tests.csproj +++ b/src/ObjectPool/test/Microsoft.Extensions.ObjectPool.Tests.csproj @@ -5,7 +5,7 @@ - + diff --git a/src/Testing/test/Microsoft.AspNetCore.Testing.Tests.csproj b/src/Testing/test/Microsoft.AspNetCore.Testing.Tests.csproj index acfb34b320..e9d0dca4ae 100644 --- a/src/Testing/test/Microsoft.AspNetCore.Testing.Tests.csproj +++ b/src/Testing/test/Microsoft.AspNetCore.Testing.Tests.csproj @@ -14,10 +14,7 @@ - - - - + diff --git a/src/WebEncoders/test/Microsoft.Extensions.WebEncoders.Tests.csproj b/src/WebEncoders/test/Microsoft.Extensions.WebEncoders.Tests.csproj index 5a28d38065..68ad589e3f 100755 --- a/src/WebEncoders/test/Microsoft.Extensions.WebEncoders.Tests.csproj +++ b/src/WebEncoders/test/Microsoft.Extensions.WebEncoders.Tests.csproj @@ -1,4 +1,4 @@ - + netcoreapp3.0;net472 @@ -6,6 +6,7 @@ + From 867453a06dfcffedb24cbd39bbb513773ce26377 Mon Sep 17 00:00:00 2001 From: Pranav K Date: Thu, 13 Jun 2019 21:54:05 -0700 Subject: [PATCH 0105/1101] Resolve the JSInterop call if deserialization fails (dotnet/extensions#1802) * Resolve the JSInterop call if deserialization fails \n\nCommit migrated from https://github.com/dotnet/extensions/commit/26cf0c9f4eb73237a460b6cc6a4912675869a0b5 --- src/JSInterop/JSInterop.slnf | 10 +++++++ .../ref/Microsoft.JSInterop.netstandard2.0.cs | 1 + .../src/DotNetDispatcher.cs | 3 +- .../Microsoft.JSInterop/src/JSException.cs | 9 ++++++ .../Microsoft.JSInterop/src/JSRuntimeBase.cs | 16 ++++++++--- .../test/DotNetDispatcherTest.cs | 18 ++++++++++++ .../test/JSRuntimeBaseTest.cs | 28 +++++++++++++++++++ 7 files changed, 79 insertions(+), 6 deletions(-) create mode 100644 src/JSInterop/JSInterop.slnf diff --git a/src/JSInterop/JSInterop.slnf b/src/JSInterop/JSInterop.slnf new file mode 100644 index 0000000000..c6cbd4cb3a --- /dev/null +++ b/src/JSInterop/JSInterop.slnf @@ -0,0 +1,10 @@ +{ + "solution": { + "path": "..\\..\\Extensions.sln", + "projects": [ + "src\\JSInterop\\Microsoft.JSInterop\\src\\Microsoft.JSInterop.csproj", + "src\\JSInterop\\Microsoft.JSInterop\\test\\Microsoft.JSInterop.Tests.csproj", + "src\\JSInterop\\Mono.WebAssembly.Interop\\src\\Mono.WebAssembly.Interop.csproj", + ] + } +} diff --git a/src/JSInterop/Microsoft.JSInterop/ref/Microsoft.JSInterop.netstandard2.0.cs b/src/JSInterop/Microsoft.JSInterop/ref/Microsoft.JSInterop.netstandard2.0.cs index 1db3385bd7..c2884dc64a 100644 --- a/src/JSInterop/Microsoft.JSInterop/ref/Microsoft.JSInterop.netstandard2.0.cs +++ b/src/JSInterop/Microsoft.JSInterop/ref/Microsoft.JSInterop.netstandard2.0.cs @@ -37,6 +37,7 @@ namespace Microsoft.JSInterop public partial class JSException : System.Exception { public JSException(string message) { } + public JSException(string message, System.Exception innerException) { } } public abstract partial class JSInProcessRuntimeBase : Microsoft.JSInterop.JSRuntimeBase, Microsoft.JSInterop.IJSInProcessRuntime, Microsoft.JSInterop.IJSRuntime { diff --git a/src/JSInterop/Microsoft.JSInterop/src/DotNetDispatcher.cs b/src/JSInterop/Microsoft.JSInterop/src/DotNetDispatcher.cs index 5ecd3506e0..d7fc99f484 100644 --- a/src/JSInterop/Microsoft.JSInterop/src/DotNetDispatcher.cs +++ b/src/JSInterop/Microsoft.JSInterop/src/DotNetDispatcher.cs @@ -227,11 +227,10 @@ namespace Microsoft.JSInterop catch { // Always dispose the JsonDocument in case of an error. - jsonDocument.Dispose(); + jsonDocument?.Dispose(); throw; } - static bool IsIncorrectDotNetObjectRefUse(JsonElement item, Type parameterType) { // Check for incorrect use of DotNetObjectRef at the top level. We know it's diff --git a/src/JSInterop/Microsoft.JSInterop/src/JSException.cs b/src/JSInterop/Microsoft.JSInterop/src/JSException.cs index 2929f69311..c2e4f1b7ae 100644 --- a/src/JSInterop/Microsoft.JSInterop/src/JSException.cs +++ b/src/JSInterop/Microsoft.JSInterop/src/JSException.cs @@ -17,5 +17,14 @@ namespace Microsoft.JSInterop public JSException(string message) : base(message) { } + + /// + /// Constructs an instance of . + /// + /// The exception message. + /// The inner exception. + public JSException(string message, Exception innerException) : base(message, innerException) + { + } } } diff --git a/src/JSInterop/Microsoft.JSInterop/src/JSRuntimeBase.cs b/src/JSInterop/Microsoft.JSInterop/src/JSRuntimeBase.cs index 3e39e42e9a..e2b3875a50 100644 --- a/src/JSInterop/Microsoft.JSInterop/src/JSRuntimeBase.cs +++ b/src/JSInterop/Microsoft.JSInterop/src/JSRuntimeBase.cs @@ -94,10 +94,18 @@ namespace Microsoft.JSInterop if (succeeded) { var resultType = TaskGenericsUtil.GetTaskCompletionSourceResultType(tcs); - var result = asyncCallResult != null ? - JsonSerializer.Parse(asyncCallResult.JsonElement.GetRawText(), resultType, JsonSerializerOptionsProvider.Options) : - null; - TaskGenericsUtil.SetTaskCompletionSourceResult(tcs, result); + try + { + var result = asyncCallResult != null ? + JsonSerializer.Parse(asyncCallResult.JsonElement.GetRawText(), resultType, JsonSerializerOptionsProvider.Options) : + null; + TaskGenericsUtil.SetTaskCompletionSourceResult(tcs, result); + } + catch (Exception exception) + { + var message = $"An exception occurred executing JS interop: {exception.Message}. See InnerException for more details."; + TaskGenericsUtil.SetTaskCompletionSourceException(tcs, new JSException(message, exception)); + } } else { diff --git a/src/JSInterop/Microsoft.JSInterop/test/DotNetDispatcherTest.cs b/src/JSInterop/Microsoft.JSInterop/test/DotNetDispatcherTest.cs index c65b4e4680..7854122feb 100644 --- a/src/JSInterop/Microsoft.JSInterop/test/DotNetDispatcherTest.cs +++ b/src/JSInterop/Microsoft.JSInterop/test/DotNetDispatcherTest.cs @@ -363,6 +363,24 @@ namespace Microsoft.JSInterop.Tests Assert.Contains(nameof(ThrowingClass.AsyncThrowingMethod), exception); }); + [Fact] + public Task BeginInvoke_ThrowsWithInvalidArgsJson_WithCallId() => WithJSRuntime(async jsRuntime => + { + // Arrange + var callId = "123"; + var resultTask = jsRuntime.NextInvocationTask; + DotNetDispatcher.BeginInvoke(callId, thisAssemblyName, "InvocableStaticWithParams", default, "not json"); + + await resultTask; // This won't throw, it sets properties on the jsRuntime. + + // Assert + using var jsonDocument = JsonDocument.Parse(jsRuntime.LastInvocationArgsJson); + var result = jsonDocument.RootElement; + Assert.Equal(callId, result[0].GetString()); + Assert.False(result[1].GetBoolean()); // Fails + Assert.Contains("JsonReaderException: '<' is an invalid start of a value.", result[2].GetString()); + }); + Task WithJSRuntime(Action testCode) { return WithJSRuntime(jsRuntime => diff --git a/src/JSInterop/Microsoft.JSInterop/test/JSRuntimeBaseTest.cs b/src/JSInterop/Microsoft.JSInterop/test/JSRuntimeBaseTest.cs index 82126836d8..4e0818219b 100644 --- a/src/JSInterop/Microsoft.JSInterop/test/JSRuntimeBaseTest.cs +++ b/src/JSInterop/Microsoft.JSInterop/test/JSRuntimeBaseTest.cs @@ -5,6 +5,7 @@ using System; using System.Collections.Generic; using System.Linq; using System.Text.Json; +using System.Threading.Tasks; using Microsoft.JSInterop.Internal; using Xunit; @@ -86,6 +87,33 @@ namespace Microsoft.JSInterop.Tests Assert.Equal("This is a test exception", ((JSException)task.Exception.InnerException).Message); } + [Fact] + public async Task CanCompleteAsyncCallsWithErrorsDuringDeserialization() + { + // Arrange + var runtime = new TestJSRuntime(); + + // Act/Assert: Tasks not initially completed + var unrelatedTask = runtime.InvokeAsync("unrelated call", Array.Empty()); + var task = runtime.InvokeAsync("test identifier", Array.Empty()); + Assert.False(unrelatedTask.IsCompleted); + Assert.False(task.IsCompleted); + using var jsonDocument = JsonDocument.Parse("\"Not a string\""); + + // Act/Assert: Task can be failed + runtime.OnEndInvoke( + runtime.BeginInvokeCalls[1].AsyncHandle, + /* succeeded: */ true, + new JSAsyncCallResult(jsonDocument, jsonDocument.RootElement)); + Assert.False(unrelatedTask.IsCompleted); + + var jsException = await Assert.ThrowsAsync(() => task); + Assert.IsType(jsException.InnerException); + + // Verify we've disposed the JsonDocument. + Assert.Throws(() => jsonDocument.RootElement.Type); + } + [Fact] public void CannotCompleteSameAsyncCallMoreThanOnce() { From 2a890a978f99a6ea23839eb722b39c0a51919c3e Mon Sep 17 00:00:00 2001 From: Nate McMaster Date: Fri, 14 Jun 2019 12:13:49 -0700 Subject: [PATCH 0106/1101] Set ReferenceAssemblyAttribute in reference assemblies (dotnet/extensions#1845) \n\nCommit migrated from https://github.com/dotnet/extensions/commit/9107db48230c79bca89db510f6306d92ee451e5d --- .../test/Shared.Tests/Microsoft.AspNetCore.Shared.Tests.csproj | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Shared/test/Shared.Tests/Microsoft.AspNetCore.Shared.Tests.csproj b/src/Shared/test/Shared.Tests/Microsoft.AspNetCore.Shared.Tests.csproj index 222c88bc67..daf7eef7ea 100644 --- a/src/Shared/test/Shared.Tests/Microsoft.AspNetCore.Shared.Tests.csproj +++ b/src/Shared/test/Shared.Tests/Microsoft.AspNetCore.Shared.Tests.csproj @@ -8,6 +8,7 @@ From b39ec064676bb12553252502a99ba8e06443cd4c Mon Sep 17 00:00:00 2001 From: Brennan Date: Thu, 27 Jun 2019 10:21:07 -0700 Subject: [PATCH 0107/1101] Update corefx dependencies (dotnet/extensions#1898) \n\nCommit migrated from https://github.com/dotnet/extensions/commit/b398a5db3d2cd1ed6700867edaecfe41ac721918 --- .../src/DotNetDispatcher.cs | 10 +++++----- .../src/JSInProcessRuntimeBase.cs | 4 ++-- .../Microsoft.JSInterop/src/JSRuntimeBase.cs | 6 +++--- .../test/DotNetDispatcherTest.cs | 18 +++++++++--------- .../test/JSRuntimeBaseTest.cs | 2 +- 5 files changed, 20 insertions(+), 20 deletions(-) diff --git a/src/JSInterop/Microsoft.JSInterop/src/DotNetDispatcher.cs b/src/JSInterop/Microsoft.JSInterop/src/DotNetDispatcher.cs index d7fc99f484..360efae5d1 100644 --- a/src/JSInterop/Microsoft.JSInterop/src/DotNetDispatcher.cs +++ b/src/JSInterop/Microsoft.JSInterop/src/DotNetDispatcher.cs @@ -51,7 +51,7 @@ namespace Microsoft.JSInterop return null; } - return JsonSerializer.ToString(syncResult, JsonSerializerOptionsProvider.Options); + return JsonSerializer.Serialize(syncResult, JsonSerializerOptionsProvider.Options); } /// @@ -177,9 +177,9 @@ namespace Microsoft.JSInterop var shouldDisposeJsonDocument = true; try { - if (jsonDocument.RootElement.Type != JsonValueType.Array) + if (jsonDocument.RootElement.ValueKind != JsonValueKind.Array) { - throw new ArgumentException($"Expected a JSON array but got {jsonDocument.RootElement.Type}."); + throw new ArgumentException($"Expected a JSON array but got {jsonDocument.RootElement.ValueKind}."); } var suppliedArgsLength = jsonDocument.RootElement.GetArrayLength(); @@ -211,7 +211,7 @@ namespace Microsoft.JSInterop } else { - suppliedArgs[index] = JsonSerializer.Parse(item.GetRawText(), parameterType, JsonSerializerOptionsProvider.Options); + suppliedArgs[index] = JsonSerializer.Deserialize(item.GetRawText(), parameterType, JsonSerializerOptionsProvider.Options); } index++; @@ -236,7 +236,7 @@ namespace Microsoft.JSInterop // Check for incorrect use of DotNetObjectRef at the top level. We know it's // an incorrect use if there's a object that looks like { '__dotNetObject': }, // but we aren't assigning to DotNetObjectRef{T}. - return item.Type == JsonValueType.Object && + return item.ValueKind == JsonValueKind.Object && item.TryGetProperty(DotNetObjectRefKey, out _) && !typeof(IDotNetObjectRef).IsAssignableFrom(parameterType); } diff --git a/src/JSInterop/Microsoft.JSInterop/src/JSInProcessRuntimeBase.cs b/src/JSInterop/Microsoft.JSInterop/src/JSInProcessRuntimeBase.cs index 2565496a54..7606f1865f 100644 --- a/src/JSInterop/Microsoft.JSInterop/src/JSInProcessRuntimeBase.cs +++ b/src/JSInterop/Microsoft.JSInterop/src/JSInProcessRuntimeBase.cs @@ -19,13 +19,13 @@ namespace Microsoft.JSInterop /// An instance of obtained by JSON-deserializing the return value. public TValue Invoke(string identifier, params object[] args) { - var resultJson = InvokeJS(identifier, JsonSerializer.ToString(args, JsonSerializerOptionsProvider.Options)); + var resultJson = InvokeJS(identifier, JsonSerializer.Serialize(args, JsonSerializerOptionsProvider.Options)); if (resultJson is null) { return default; } - return JsonSerializer.Parse(resultJson, JsonSerializerOptionsProvider.Options); + return JsonSerializer.Deserialize(resultJson, JsonSerializerOptionsProvider.Options); } /// diff --git a/src/JSInterop/Microsoft.JSInterop/src/JSRuntimeBase.cs b/src/JSInterop/Microsoft.JSInterop/src/JSRuntimeBase.cs index e2b3875a50..50fd7e2839 100644 --- a/src/JSInterop/Microsoft.JSInterop/src/JSRuntimeBase.cs +++ b/src/JSInterop/Microsoft.JSInterop/src/JSRuntimeBase.cs @@ -42,7 +42,7 @@ namespace Microsoft.JSInterop try { var argsJson = args?.Length > 0 ? - JsonSerializer.ToString(args, JsonSerializerOptionsProvider.Options) : + JsonSerializer.Serialize(args, JsonSerializerOptionsProvider.Options) : null; BeginInvokeJS(taskId, identifier, argsJson); return tcs.Task; @@ -78,7 +78,7 @@ namespace Microsoft.JSInterop // We pass 0 as the async handle because we don't want the JS-side code to // send back any notification (we're just providing a result for an existing async call) - var args = JsonSerializer.ToString(new[] { callId, success, resultOrException }, JsonSerializerOptionsProvider.Options); + var args = JsonSerializer.Serialize(new[] { callId, success, resultOrException }, JsonSerializerOptionsProvider.Options); BeginInvokeJS(0, "DotNet.jsCallDispatcher.endInvokeDotNetFromJS", args); } @@ -97,7 +97,7 @@ namespace Microsoft.JSInterop try { var result = asyncCallResult != null ? - JsonSerializer.Parse(asyncCallResult.JsonElement.GetRawText(), resultType, JsonSerializerOptionsProvider.Options) : + JsonSerializer.Deserialize(asyncCallResult.JsonElement.GetRawText(), resultType, JsonSerializerOptionsProvider.Options) : null; TaskGenericsUtil.SetTaskCompletionSourceResult(tcs, result); } diff --git a/src/JSInterop/Microsoft.JSInterop/test/DotNetDispatcherTest.cs b/src/JSInterop/Microsoft.JSInterop/test/DotNetDispatcherTest.cs index 7854122feb..4e8733e067 100644 --- a/src/JSInterop/Microsoft.JSInterop/test/DotNetDispatcherTest.cs +++ b/src/JSInterop/Microsoft.JSInterop/test/DotNetDispatcherTest.cs @@ -89,7 +89,7 @@ namespace Microsoft.JSInterop.Tests { // Arrange/Act var resultJson = DotNetDispatcher.Invoke(thisAssemblyName, "InvocableStaticNonVoid", default, null); - var result = JsonSerializer.Parse(resultJson, JsonSerializerOptionsProvider.Options); + var result = JsonSerializer.Deserialize(resultJson, JsonSerializerOptionsProvider.Options); // Assert Assert.Equal("Test", result.StringVal); @@ -101,7 +101,7 @@ namespace Microsoft.JSInterop.Tests { // Arrange/Act var resultJson = DotNetDispatcher.Invoke(thisAssemblyName, nameof(SomePublicType.InvokableMethodWithoutCustomIdentifier), default, null); - var result = JsonSerializer.Parse(resultJson, JsonSerializerOptionsProvider.Options); + var result = JsonSerializer.Deserialize(resultJson, JsonSerializerOptionsProvider.Options); // Assert Assert.Equal("InvokableMethodWithoutCustomIdentifier", result.StringVal); @@ -117,7 +117,7 @@ namespace Microsoft.JSInterop.Tests jsRuntime.Invoke("unimportant", objectRef); // Arrange: Remaining args - var argsJson = JsonSerializer.ToString(new object[] + var argsJson = JsonSerializer.Serialize(new object[] { new TestDTO { StringVal = "Another string", IntVal = 456 }, new[] { 100, 200 }, @@ -130,7 +130,7 @@ namespace Microsoft.JSInterop.Tests var root = result.RootElement; // Assert: First result value marshalled via JSON - var resultDto1 = JsonSerializer.Parse(root[0].GetRawText(), JsonSerializerOptionsProvider.Options); + var resultDto1 = JsonSerializer.Deserialize(root[0].GetRawText(), JsonSerializerOptionsProvider.Options); Assert.Equal("ANOTHER STRING", resultDto1.StringVal); Assert.Equal(756, resultDto1.IntVal); @@ -156,7 +156,7 @@ namespace Microsoft.JSInterop.Tests jsRuntime.Invoke("unimportant", objectRef); // Arrange: Remaining args - var argsJson = JsonSerializer.ToString(new object[] + var argsJson = JsonSerializer.Serialize(new object[] { new TestDTO { StringVal = "Another string", IntVal = 456 }, new[] { 100, 200 }, @@ -262,7 +262,7 @@ namespace Microsoft.JSInterop.Tests public void CannotInvokeWithIncorrectNumberOfParams() { // Arrange - var argsJson = JsonSerializer.ToString(new object[] { 1, 2, 3, 4 }, JsonSerializerOptionsProvider.Options); + var argsJson = JsonSerializer.Serialize(new object[] { 1, 2, 3, 4 }, JsonSerializerOptionsProvider.Options); // Act/Assert var ex = Assert.Throws(() => @@ -284,7 +284,7 @@ namespace Microsoft.JSInterop.Tests jsRuntime.Invoke("unimportant", arg1Ref, arg2Ref); // Arrange: all args - var argsJson = JsonSerializer.ToString(new object[] + var argsJson = JsonSerializer.Serialize(new object[] { new TestDTO { IntVal = 1000, StringVal = "String via JSON" }, arg2Ref, @@ -306,12 +306,12 @@ namespace Microsoft.JSInterop.Tests Assert.True(result[1].GetBoolean()); // Success flag // Assert: First result value marshalled via JSON - var resultDto1 = JsonSerializer.Parse(resultValue[0].GetRawText(), JsonSerializerOptionsProvider.Options); + var resultDto1 = JsonSerializer.Deserialize(resultValue[0].GetRawText(), JsonSerializerOptionsProvider.Options); Assert.Equal("STRING VIA JSON", resultDto1.StringVal); Assert.Equal(2000, resultDto1.IntVal); // Assert: Second result value marshalled by ref - var resultDto2Ref = JsonSerializer.Parse>(resultValue[1].GetRawText(), JsonSerializerOptionsProvider.Options); + var resultDto2Ref = JsonSerializer.Deserialize>(resultValue[1].GetRawText(), JsonSerializerOptionsProvider.Options); var resultDto2 = resultDto2Ref.Value; Assert.Equal("MY STRING", resultDto2.StringVal); Assert.Equal(2468, resultDto2.IntVal); diff --git a/src/JSInterop/Microsoft.JSInterop/test/JSRuntimeBaseTest.cs b/src/JSInterop/Microsoft.JSInterop/test/JSRuntimeBaseTest.cs index 4e0818219b..afbe5d0595 100644 --- a/src/JSInterop/Microsoft.JSInterop/test/JSRuntimeBaseTest.cs +++ b/src/JSInterop/Microsoft.JSInterop/test/JSRuntimeBaseTest.cs @@ -111,7 +111,7 @@ namespace Microsoft.JSInterop.Tests Assert.IsType(jsException.InnerException); // Verify we've disposed the JsonDocument. - Assert.Throws(() => jsonDocument.RootElement.Type); + Assert.Throws(() => jsonDocument.RootElement.ValueKind); } [Fact] From 244ed547644047a0696eba8955f6a407fb30a338 Mon Sep 17 00:00:00 2001 From: Javier Calvarro Nelson Date: Tue, 2 Jul 2019 12:40:50 +0200 Subject: [PATCH 0108/1101] Allow sanitization of dotnet interop exceptions (dotnet/extensions#1879) Adds the ability to sanitize exceptions during JS to .NET interop calls by overriding OnDotNetInvocationException method and returning an object to be returned to JavaScript that describes the exception.\n\nCommit migrated from https://github.com/dotnet/extensions/commit/d7eab7c0830382d435aef2cc22faf90c39b8fb54 --- .../ref/Microsoft.JSInterop.netstandard2.0.cs | 1 + .../src/DotNetDispatcher.cs | 9 ++-- .../Microsoft.JSInterop/src/JSRuntimeBase.cs | 21 +++++++-- .../test/JSRuntimeBaseTest.cs | 44 +++++++++++++++++++ 4 files changed, 67 insertions(+), 8 deletions(-) diff --git a/src/JSInterop/Microsoft.JSInterop/ref/Microsoft.JSInterop.netstandard2.0.cs b/src/JSInterop/Microsoft.JSInterop/ref/Microsoft.JSInterop.netstandard2.0.cs index c2884dc64a..cd383df0df 100644 --- a/src/JSInterop/Microsoft.JSInterop/ref/Microsoft.JSInterop.netstandard2.0.cs +++ b/src/JSInterop/Microsoft.JSInterop/ref/Microsoft.JSInterop.netstandard2.0.cs @@ -61,6 +61,7 @@ namespace Microsoft.JSInterop protected JSRuntimeBase() { } protected abstract void BeginInvokeJS(long asyncHandle, string identifier, string argsJson); public System.Threading.Tasks.Task InvokeAsync(string identifier, params object[] args) { throw null; } + protected virtual object OnDotNetInvocationException(System.Exception exception, string assemblyName, string methodIdentifier) { throw null; } } } namespace Microsoft.JSInterop.Internal diff --git a/src/JSInterop/Microsoft.JSInterop/src/DotNetDispatcher.cs b/src/JSInterop/Microsoft.JSInterop/src/DotNetDispatcher.cs index 360efae5d1..4359cd76c7 100644 --- a/src/JSInterop/Microsoft.JSInterop/src/DotNetDispatcher.cs +++ b/src/JSInterop/Microsoft.JSInterop/src/DotNetDispatcher.cs @@ -103,7 +103,7 @@ namespace Microsoft.JSInterop else if (syncException != null) { // Threw synchronously, let's respond. - jsRuntimeBaseInstance.EndInvokeDotNet(callId, false, syncException); + jsRuntimeBaseInstance.EndInvokeDotNet(callId, false, syncException, assemblyName, methodIdentifier); } else if (syncResult is Task task) { @@ -114,16 +114,17 @@ namespace Microsoft.JSInterop if (t.Exception != null) { var exception = t.Exception.GetBaseException(); - jsRuntimeBaseInstance.EndInvokeDotNet(callId, false, ExceptionDispatchInfo.Capture(exception)); + + jsRuntimeBaseInstance.EndInvokeDotNet(callId, false, ExceptionDispatchInfo.Capture(exception), assemblyName, methodIdentifier); } var result = TaskGenericsUtil.GetTaskResult(task); - jsRuntimeBaseInstance.EndInvokeDotNet(callId, true, result); + jsRuntimeBaseInstance.EndInvokeDotNet(callId, true, result, assemblyName, methodIdentifier); }, TaskScheduler.Current); } else { - jsRuntimeBaseInstance.EndInvokeDotNet(callId, true, syncResult); + jsRuntimeBaseInstance.EndInvokeDotNet(callId, true, syncResult, assemblyName, methodIdentifier); } } diff --git a/src/JSInterop/Microsoft.JSInterop/src/JSRuntimeBase.cs b/src/JSInterop/Microsoft.JSInterop/src/JSRuntimeBase.cs index 50fd7e2839..694b0cb625 100644 --- a/src/JSInterop/Microsoft.JSInterop/src/JSRuntimeBase.cs +++ b/src/JSInterop/Microsoft.JSInterop/src/JSRuntimeBase.cs @@ -62,18 +62,31 @@ namespace Microsoft.JSInterop /// A JSON representation of the arguments. protected abstract void BeginInvokeJS(long asyncHandle, string identifier, string argsJson); - internal void EndInvokeDotNet(string callId, bool success, object resultOrException) + /// + /// Allows derived classes to configure the information about an exception in a JS interop call that gets sent to JavaScript. + /// + /// + /// This callback can be used in remote JS interop scenarios to sanitize exceptions that happen on the server to avoid disclosing + /// sensitive information to remote browser clients. + /// + /// The exception that occurred. + /// The assembly for the invoked .NET method. + /// The identifier for the invoked .NET method. + /// An object containing information about the exception. + protected virtual object OnDotNetInvocationException(Exception exception, string assemblyName, string methodIdentifier) => exception.ToString(); + + internal void EndInvokeDotNet(string callId, bool success, object resultOrException, string assemblyName, string methodIdentifier) { // For failures, the common case is to call EndInvokeDotNet with the Exception object. // For these we'll serialize as something that's useful to receive on the JS side. // If the value is not an Exception, we'll just rely on it being directly JSON-serializable. - if (!success && resultOrException is Exception) + if (!success && resultOrException is Exception ex) { - resultOrException = resultOrException.ToString(); + resultOrException = OnDotNetInvocationException(ex, assemblyName, methodIdentifier); } else if (!success && resultOrException is ExceptionDispatchInfo edi) { - resultOrException = edi.SourceException.ToString(); + resultOrException = OnDotNetInvocationException(edi.SourceException, assemblyName, methodIdentifier); } // We pass 0 as the async handle because we don't want the JS-side code to diff --git a/src/JSInterop/Microsoft.JSInterop/test/JSRuntimeBaseTest.cs b/src/JSInterop/Microsoft.JSInterop/test/JSRuntimeBaseTest.cs index afbe5d0595..b01ef59998 100644 --- a/src/JSInterop/Microsoft.JSInterop/test/JSRuntimeBaseTest.cs +++ b/src/JSInterop/Microsoft.JSInterop/test/JSRuntimeBaseTest.cs @@ -168,6 +168,38 @@ namespace Microsoft.JSInterop.Tests Assert.Same(obj3, runtime.ObjectRefManager.FindDotNetObject(4)); } + [Fact] + public void CanSanitizeDotNetInteropExceptions() + { + // Arrange + var expectedMessage = "An error ocurred while invoking '[Assembly]::Method'. Swapping to 'Development' environment will " + + "display more detailed information about the error that occurred."; + + string GetMessage(string assembly, string method) => $"An error ocurred while invoking '[{assembly}]::{method}'. Swapping to 'Development' environment will " + + "display more detailed information about the error that occurred."; + + var runtime = new TestJSRuntime() + { + OnDotNetException = (e, a, m) => new JSError { Message = GetMessage(a, m) } + }; + + var exception = new Exception("Some really sensitive data in here"); + + // Act + runtime.EndInvokeDotNet("0", false, exception, "Assembly", "Method"); + + // Assert + var call = runtime.BeginInvokeCalls.Single(); + Assert.Equal(0, call.AsyncHandle); + Assert.Equal("DotNet.jsCallDispatcher.endInvokeDotNetFromJS", call.Identifier); + Assert.Equal($"[\"0\",false,{{\"message\":\"{expectedMessage.Replace("'", "\\u0027")}\"}}]", call.ArgsJson); + } + + private class JSError + { + public string Message { get; set; } + } + class TestJSRuntime : JSRuntimeBase { public List BeginInvokeCalls = new List(); @@ -179,6 +211,18 @@ namespace Microsoft.JSInterop.Tests public string ArgsJson { get; set; } } + public Func OnDotNetException { get; set; } + + protected override object OnDotNetInvocationException(Exception exception, string assemblyName, string methodName) + { + if (OnDotNetException != null) + { + return OnDotNetException(exception, assemblyName, methodName); + } + + return base.OnDotNetInvocationException(exception, assemblyName, methodName); + } + protected override void BeginInvokeJS(long asyncHandle, string identifier, string argsJson) { BeginInvokeCalls.Add(new BeginInvokeAsyncArgs From 5e4f1f5fb2e70651aef0dee4068eac7df9504c31 Mon Sep 17 00:00:00 2001 From: Javier Calvarro Nelson Date: Tue, 2 Jul 2019 13:24:29 +0200 Subject: [PATCH 0109/1101] Adds an opt-in default timeout for async JS calls (dotnet/extensions#1880) * Adds the ability to configure a default timeout for JavaScript interop calls. * Adds the ability to pass in a cancellation token to InvokeAsync that will be used instead of the default timeout. * A default cancellation token can be passed to signal no cancellation, but it is not recommended in remote interop scenarios.\n\nCommit migrated from https://github.com/dotnet/extensions/commit/e04faac7e4a71ae03e31bcbe923f8743eefad9f7 --- .../ref/Microsoft.JSInterop.netstandard2.0.cs | 4 +- .../Microsoft.JSInterop/src/JSRuntimeBase.cs | 109 ++++++++++++++---- .../test/JSRuntimeBaseTest.cs | 88 ++++++++++++-- 3 files changed, 169 insertions(+), 32 deletions(-) diff --git a/src/JSInterop/Microsoft.JSInterop/ref/Microsoft.JSInterop.netstandard2.0.cs b/src/JSInterop/Microsoft.JSInterop/ref/Microsoft.JSInterop.netstandard2.0.cs index cd383df0df..b0f187bcd1 100644 --- a/src/JSInterop/Microsoft.JSInterop/ref/Microsoft.JSInterop.netstandard2.0.cs +++ b/src/JSInterop/Microsoft.JSInterop/ref/Microsoft.JSInterop.netstandard2.0.cs @@ -59,7 +59,9 @@ namespace Microsoft.JSInterop public abstract partial class JSRuntimeBase : Microsoft.JSInterop.IJSRuntime { protected JSRuntimeBase() { } - protected abstract void BeginInvokeJS(long asyncHandle, string identifier, string argsJson); + protected System.TimeSpan? DefaultAsyncTimeout { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } + protected abstract void BeginInvokeJS(long taskId, string identifier, string argsJson); + public System.Threading.Tasks.Task InvokeAsync(string identifier, System.Collections.Generic.IEnumerable args, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) { throw null; } public System.Threading.Tasks.Task InvokeAsync(string identifier, params object[] args) { throw null; } protected virtual object OnDotNetInvocationException(System.Exception exception, string assemblyName, string methodIdentifier) { throw null; } } diff --git a/src/JSInterop/Microsoft.JSInterop/src/JSRuntimeBase.cs b/src/JSInterop/Microsoft.JSInterop/src/JSRuntimeBase.cs index 694b0cb625..562a6a27ba 100644 --- a/src/JSInterop/Microsoft.JSInterop/src/JSRuntimeBase.cs +++ b/src/JSInterop/Microsoft.JSInterop/src/JSRuntimeBase.cs @@ -3,6 +3,8 @@ using System; using System.Collections.Concurrent; +using System.Collections.Generic; +using System.Linq; using System.Runtime.ExceptionServices; using System.Text.Json; using System.Threading; @@ -20,8 +22,71 @@ namespace Microsoft.JSInterop private readonly ConcurrentDictionary _pendingTasks = new ConcurrentDictionary(); + private readonly ConcurrentDictionary _cancellationRegistrations = + new ConcurrentDictionary(); + internal DotNetObjectRefManager ObjectRefManager { get; } = new DotNetObjectRefManager(); + /// + /// Gets or sets the default timeout for asynchronous JavaScript calls. + /// + protected TimeSpan? DefaultAsyncTimeout { get; set; } + + /// + /// Invokes the specified JavaScript function asynchronously. + /// + /// The JSON-serializable return type. + /// An identifier for the function to invoke. For example, the value "someScope.someFunction" will invoke the function window.someScope.someFunction. + /// JSON-serializable arguments. + /// A cancellation token to signal the cancellation of the operation. + /// An instance of obtained by JSON-deserializing the return value. + public Task InvokeAsync(string identifier, IEnumerable args, CancellationToken cancellationToken = default) + { + var taskId = Interlocked.Increment(ref _nextPendingTaskId); + var tcs = new TaskCompletionSource(TaskContinuationOptions.RunContinuationsAsynchronously); + if (cancellationToken != default) + { + _cancellationRegistrations[taskId] = cancellationToken.Register(() => + { + tcs.TrySetCanceled(cancellationToken); + CleanupTasksAndRegistrations(taskId); + }); + } + _pendingTasks[taskId] = tcs; + + try + { + if (cancellationToken.IsCancellationRequested) + { + tcs.TrySetCanceled(cancellationToken); + CleanupTasksAndRegistrations(taskId); + + return tcs.Task; + } + + var argsJson = args?.Any() == true ? + JsonSerializer.Serialize(args, JsonSerializerOptionsProvider.Options) : + null; + BeginInvokeJS(taskId, identifier, argsJson); + + return tcs.Task; + } + catch + { + CleanupTasksAndRegistrations(taskId); + throw; + } + } + + private void CleanupTasksAndRegistrations(long taskId) + { + _pendingTasks.TryRemove(taskId, out _); + if (_cancellationRegistrations.TryRemove(taskId, out var registration)) + { + registration.Dispose(); + } + } + /// /// Invokes the specified JavaScript function asynchronously. /// @@ -31,36 +96,32 @@ namespace Microsoft.JSInterop /// An instance of obtained by JSON-deserializing the return value. public Task InvokeAsync(string identifier, params object[] args) { - // We might consider also adding a default timeout here in case we don't want to - // risk a memory leak in the scenario where the JS-side code is failing to complete - // the operation. - - var taskId = Interlocked.Increment(ref _nextPendingTaskId); - var tcs = new TaskCompletionSource(); - _pendingTasks[taskId] = tcs; - - try + if (!DefaultAsyncTimeout.HasValue) { - var argsJson = args?.Length > 0 ? - JsonSerializer.Serialize(args, JsonSerializerOptionsProvider.Options) : - null; - BeginInvokeJS(taskId, identifier, argsJson); - return tcs.Task; + return InvokeAsync(identifier, args, default); } - catch + else { - _pendingTasks.TryRemove(taskId, out _); - throw; + return InvokeWithDefaultCancellation(identifier, args); + } + } + + private async Task InvokeWithDefaultCancellation(string identifier, IEnumerable args) + { + using (var cts = new CancellationTokenSource(DefaultAsyncTimeout.Value)) + { + // We need to await here due to the using + return await InvokeAsync(identifier, args, cts.Token); } } /// /// Begins an asynchronous function invocation. /// - /// The identifier for the function invocation, or zero if no async callback is required. + /// The identifier for the function invocation, or zero if no async callback is required. /// The identifier for the function to invoke. /// A JSON representation of the arguments. - protected abstract void BeginInvokeJS(long asyncHandle, string identifier, string argsJson); + protected abstract void BeginInvokeJS(long taskId, string identifier, string argsJson); /// /// Allows derived classes to configure the information about an exception in a JS interop call that gets sent to JavaScript. @@ -95,15 +156,19 @@ namespace Microsoft.JSInterop BeginInvokeJS(0, "DotNet.jsCallDispatcher.endInvokeDotNetFromJS", args); } - internal void EndInvokeJS(long asyncHandle, bool succeeded, JSAsyncCallResult asyncCallResult) + internal void EndInvokeJS(long taskId, bool succeeded, JSAsyncCallResult asyncCallResult) { using (asyncCallResult?.JsonDocument) { - if (!_pendingTasks.TryRemove(asyncHandle, out var tcs)) + if (!_pendingTasks.TryRemove(taskId, out var tcs)) { - throw new ArgumentException($"There is no pending task with handle '{asyncHandle}'."); + // We should simply return if we can't find an id for the invocation. + // This likely means that the method that initiated the call defined a timeout and stopped waiting. + return; } + CleanupTasksAndRegistrations(taskId); + if (succeeded) { var resultType = TaskGenericsUtil.GetTaskCompletionSourceResultType(tcs); diff --git a/src/JSInterop/Microsoft.JSInterop/test/JSRuntimeBaseTest.cs b/src/JSInterop/Microsoft.JSInterop/test/JSRuntimeBaseTest.cs index b01ef59998..3ad78c303f 100644 --- a/src/JSInterop/Microsoft.JSInterop/test/JSRuntimeBaseTest.cs +++ b/src/JSInterop/Microsoft.JSInterop/test/JSRuntimeBaseTest.cs @@ -5,6 +5,7 @@ using System; using System.Collections.Generic; using System.Linq; using System.Text.Json; +using System.Threading; using System.Threading.Tasks; using Microsoft.JSInterop.Internal; using Xunit; @@ -38,6 +39,69 @@ namespace Microsoft.JSInterop.Tests }); } + [Fact] + public async Task InvokeAsync_CancelsAsyncTask_AfterDefaultTimeout() + { + // Arrange + var runtime = new TestJSRuntime(); + runtime.DefaultTimeout = TimeSpan.FromSeconds(1); + + // Act + var task = runtime.InvokeAsync("test identifier 1", "arg1", 123, true); + + // Assert + await Assert.ThrowsAsync(async () => await task); + } + + [Fact] + public async Task InvokeAsync_CompletesSuccessfullyBeforeTimeout() + { + // Arrange + var runtime = new TestJSRuntime(); + runtime.DefaultTimeout = TimeSpan.FromSeconds(10); + + // Act + var task = runtime.InvokeAsync("test identifier 1", "arg1", 123, true); + runtime.EndInvokeJS(2, succeeded: true, null); + + // Assert + await task; + } + + [Fact] + public async Task InvokeAsync_CancelsAsyncTasksWhenCancellationTokenFires() + { + // Arrange + using var cts = new CancellationTokenSource(); + var runtime = new TestJSRuntime(); + + // Act + var task = runtime.InvokeAsync("test identifier 1", new object[] { "arg1", 123, true }, cts.Token); + + cts.Cancel(); + + // Assert + await Assert.ThrowsAsync(async () => await task); + } + + [Fact] + public async Task InvokeAsync_DoesNotStartWorkWhenCancellationHasBeenRequested() + { + // Arrange + using var cts = new CancellationTokenSource(); + cts.Cancel(); + var runtime = new TestJSRuntime(); + + // Act + var task = runtime.InvokeAsync("test identifier 1", new object[] { "arg1", 123, true }, cts.Token); + + cts.Cancel(); + + // Assert + await Assert.ThrowsAsync(async () => await task); + Assert.Empty(runtime.BeginInvokeCalls); + } + [Fact] public void CanCompleteAsyncCallsAsSuccess() { @@ -115,21 +179,19 @@ namespace Microsoft.JSInterop.Tests } [Fact] - public void CannotCompleteSameAsyncCallMoreThanOnce() + public async Task CompletingSameAsyncCallMoreThanOnce_IgnoresSecondResultAsync() { // Arrange var runtime = new TestJSRuntime(); // Act/Assert - runtime.InvokeAsync("test identifier", Array.Empty()); + var task = runtime.InvokeAsync("test identifier", Array.Empty()); var asyncHandle = runtime.BeginInvokeCalls[0].AsyncHandle; - runtime.OnEndInvoke(asyncHandle, true, null); - var ex = Assert.Throws(() => - { - // Second "end invoke" will fail - runtime.OnEndInvoke(asyncHandle, true, null); - }); - Assert.Equal($"There is no pending task with handle '{asyncHandle}'.", ex.Message); + runtime.OnEndInvoke(asyncHandle, true, new JSAsyncCallResult(JsonDocument.Parse("{}"), JsonDocument.Parse("{\"Message\": \"Some data\"}").RootElement.GetProperty("Message"))); + runtime.OnEndInvoke(asyncHandle, false, new JSAsyncCallResult(null, JsonDocument.Parse("{\"Message\": \"Exception\"}").RootElement.GetProperty("Message"))); + + var result = await task; + Assert.Equal("Some data", result); } [Fact] @@ -204,6 +266,14 @@ namespace Microsoft.JSInterop.Tests { public List BeginInvokeCalls = new List(); + public TimeSpan? DefaultTimeout + { + set + { + base.DefaultAsyncTimeout = value; + } + } + public class BeginInvokeAsyncArgs { public long AsyncHandle { get; set; } From 6069a897588812da5f895e7c4a982f14732c1c48 Mon Sep 17 00:00:00 2001 From: Javier Calvarro Nelson Date: Tue, 2 Jul 2019 19:21:16 +0200 Subject: [PATCH 0110/1101] Add InvokeAsync with cancellation token overload to IJSRuntime (dotnet/extensions#1915) Adds the InvokeAsync with cancellation token overload to IJSRuntime\n\nCommit migrated from https://github.com/dotnet/extensions/commit/55f0b77464408036d99c4fbdd2465ea3e38a4e9b --- .../ref/Microsoft.JSInterop.netstandard2.0.cs | 1 + src/JSInterop/Microsoft.JSInterop/src/IJSRuntime.cs | 13 ++++++++++++- .../Microsoft.JSInterop/test/JSRuntimeTest.cs | 5 +++++ 3 files changed, 18 insertions(+), 1 deletion(-) diff --git a/src/JSInterop/Microsoft.JSInterop/ref/Microsoft.JSInterop.netstandard2.0.cs b/src/JSInterop/Microsoft.JSInterop/ref/Microsoft.JSInterop.netstandard2.0.cs index b0f187bcd1..ad95b7342b 100644 --- a/src/JSInterop/Microsoft.JSInterop/ref/Microsoft.JSInterop.netstandard2.0.cs +++ b/src/JSInterop/Microsoft.JSInterop/ref/Microsoft.JSInterop.netstandard2.0.cs @@ -32,6 +32,7 @@ namespace Microsoft.JSInterop } public partial interface IJSRuntime { + System.Threading.Tasks.Task InvokeAsync(string identifier, System.Collections.Generic.IEnumerable args, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)); System.Threading.Tasks.Task InvokeAsync(string identifier, params object[] args); } public partial class JSException : System.Exception diff --git a/src/JSInterop/Microsoft.JSInterop/src/IJSRuntime.cs b/src/JSInterop/Microsoft.JSInterop/src/IJSRuntime.cs index 97713bb3c1..d7ee372a73 100644 --- a/src/JSInterop/Microsoft.JSInterop/src/IJSRuntime.cs +++ b/src/JSInterop/Microsoft.JSInterop/src/IJSRuntime.cs @@ -1,7 +1,8 @@ // 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.Collections.Generic; +using System.Threading; using System.Threading.Tasks; namespace Microsoft.JSInterop @@ -19,5 +20,15 @@ namespace Microsoft.JSInterop /// JSON-serializable arguments. /// An instance of obtained by JSON-deserializing the return value. Task InvokeAsync(string identifier, params object[] args); + + /// + /// Invokes the specified JavaScript function asynchronously. + /// + /// The JSON-serializable return type. + /// An identifier for the function to invoke. For example, the value "someScope.someFunction" will invoke the function window.someScope.someFunction. + /// JSON-serializable arguments. + /// A cancellation token to signal the cancellation of the operation. + /// An instance of obtained by JSON-deserializing the return value. + Task InvokeAsync(string identifier, IEnumerable args, CancellationToken cancellationToken = default); } } diff --git a/src/JSInterop/Microsoft.JSInterop/test/JSRuntimeTest.cs b/src/JSInterop/Microsoft.JSInterop/test/JSRuntimeTest.cs index ca8c96df60..f2fab5d741 100644 --- a/src/JSInterop/Microsoft.JSInterop/test/JSRuntimeTest.cs +++ b/src/JSInterop/Microsoft.JSInterop/test/JSRuntimeTest.cs @@ -2,7 +2,9 @@ // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; +using System.Collections.Generic; using System.Linq; +using System.Threading; using System.Threading.Tasks; using Xunit; @@ -29,6 +31,9 @@ namespace Microsoft.JSInterop.Tests { public Task InvokeAsync(string identifier, params object[] args) => throw new NotImplementedException(); + + public Task InvokeAsync(string identifier, IEnumerable args, CancellationToken cancellationToken = default) => + throw new NotImplementedException(); } } } From 919734cbb2fdb3e412ce0379f0395e219effc5d3 Mon Sep 17 00:00:00 2001 From: Javier Calvarro Nelson Date: Fri, 5 Jul 2019 10:16:39 +0200 Subject: [PATCH 0111/1101] Invoking a method with an invalid dotnetobjectref bubbles up through the wrong path (dotnet/extensions#1940) * Handle non existing dotnet object references and returns the error through JSInterop instead of bubbling it up to the caller. * Every other error in JSInterop is returned that way. * It makes sure that the error gets to the client and is sanitized appropriately if necessary.\n\nCommit migrated from https://github.com/dotnet/extensions/commit/a30f2cbb278bb8490b32818b9af51ba64d8fba9d --- .../Microsoft.JSInterop/src/DotNetDispatcher.cs | 11 ++++++----- .../test/DotNetDispatcherTest.cs | 17 +++++++++++++++++ 2 files changed, 23 insertions(+), 5 deletions(-) diff --git a/src/JSInterop/Microsoft.JSInterop/src/DotNetDispatcher.cs b/src/JSInterop/Microsoft.JSInterop/src/DotNetDispatcher.cs index 4359cd76c7..952a6061ab 100644 --- a/src/JSInterop/Microsoft.JSInterop/src/DotNetDispatcher.cs +++ b/src/JSInterop/Microsoft.JSInterop/src/DotNetDispatcher.cs @@ -75,19 +75,20 @@ namespace Microsoft.JSInterop // code has to implement its own way of returning async results. var jsRuntimeBaseInstance = (JSRuntimeBase)JSRuntime.Current; - var targetInstance = (object)null; - if (dotNetObjectId != default) - { - targetInstance = DotNetObjectRefManager.Current.FindDotNetObject(dotNetObjectId); - } // Using ExceptionDispatchInfo here throughout because we want to always preserve // original stack traces. object syncResult = null; ExceptionDispatchInfo syncException = null; + object targetInstance = null; try { + if (dotNetObjectId != default) + { + targetInstance = DotNetObjectRefManager.Current.FindDotNetObject(dotNetObjectId); + } + syncResult = InvokeSynchronously(assemblyName, methodIdentifier, targetInstance, argsJson); } catch (Exception ex) diff --git a/src/JSInterop/Microsoft.JSInterop/test/DotNetDispatcherTest.cs b/src/JSInterop/Microsoft.JSInterop/test/DotNetDispatcherTest.cs index 4e8733e067..9e8a7b672e 100644 --- a/src/JSInterop/Microsoft.JSInterop/test/DotNetDispatcherTest.cs +++ b/src/JSInterop/Microsoft.JSInterop/test/DotNetDispatcherTest.cs @@ -381,6 +381,23 @@ namespace Microsoft.JSInterop.Tests Assert.Contains("JsonReaderException: '<' is an invalid start of a value.", result[2].GetString()); }); + [Fact] + public Task BeginInvoke_ThrowsWithInvalid_DotNetObjectRef() => WithJSRuntime(jsRuntime => + { + // Arrange + var callId = "123"; + var resultTask = jsRuntime.NextInvocationTask; + DotNetDispatcher.BeginInvoke(callId, null, "InvokableInstanceVoid", 1, null); + + // Assert + using var jsonDocument = JsonDocument.Parse(jsRuntime.LastInvocationArgsJson); + var result = jsonDocument.RootElement; + Assert.Equal(callId, result[0].GetString()); + Assert.False(result[1].GetBoolean()); // Fails + + Assert.StartsWith("System.ArgumentException: There is no tracked object with id '1'. Perhaps the DotNetObjectRef instance was already disposed.", result[2].GetString()); + }); + Task WithJSRuntime(Action testCode) { return WithJSRuntime(jsRuntime => From a61d9b6921c0637581f50e5945f4be759d9bab44 Mon Sep 17 00:00:00 2001 From: Ryan Brandenburg Date: Thu, 27 Jun 2019 15:58:43 -0700 Subject: [PATCH 0112/1101] Add some missing doc comments \n\nCommit migrated from https://github.com/dotnet/extensions/commit/238b272238ff4382df9a2fc5c7d92a0d68aafa97 --- .../Abstractions/src/LocalizedString.cs | 10 +++++++--- .../src/StringLocalizerExtensions.cs | 7 +++++-- .../Localization/src/LocalizationOptions.cs | 6 ++++++ .../Localization/src/ResourceNamesCache.cs | 11 +++++++++-- src/ObjectPool/src/DefaultObjectPool.cs | 13 +++++++++++++ src/ObjectPool/src/DefaultObjectPoolProvider.cs | 7 +++++++ src/ObjectPool/src/IPooledObjectPolicy.cs | 13 +++++++++++++ src/ObjectPool/src/ObjectPool.cs | 16 ++++++++++++++++ src/ObjectPool/src/ObjectPoolProvider.cs | 11 +++++++++++ 9 files changed, 87 insertions(+), 7 deletions(-) diff --git a/src/Localization/Abstractions/src/LocalizedString.cs b/src/Localization/Abstractions/src/LocalizedString.cs index 6556da40a0..7cac58d16a 100644 --- a/src/Localization/Abstractions/src/LocalizedString.cs +++ b/src/Localization/Abstractions/src/LocalizedString.cs @@ -1,5 +1,5 @@ -// 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. +// 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; @@ -56,6 +56,10 @@ namespace Microsoft.Extensions.Localization SearchedLocation = searchedLocation; } + /// + /// Implicitly converts the to a . + /// + /// The string to be implicitly converted. public static implicit operator string(LocalizedString localizedString) { return localizedString?.Value; @@ -87,4 +91,4 @@ namespace Microsoft.Extensions.Localization /// The actual string. public override string ToString() => Value; } -} \ No newline at end of file +} diff --git a/src/Localization/Abstractions/src/StringLocalizerExtensions.cs b/src/Localization/Abstractions/src/StringLocalizerExtensions.cs index bde47f74f3..3a57475334 100644 --- a/src/Localization/Abstractions/src/StringLocalizerExtensions.cs +++ b/src/Localization/Abstractions/src/StringLocalizerExtensions.cs @@ -1,11 +1,14 @@ -// 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. +// 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.Collections.Generic; namespace Microsoft.Extensions.Localization { + /// + /// Extension methods for operating on 's. + /// public static class StringLocalizerExtensions { /// diff --git a/src/Localization/Localization/src/LocalizationOptions.cs b/src/Localization/Localization/src/LocalizationOptions.cs index 1b7408fe67..1c0b82d210 100644 --- a/src/Localization/Localization/src/LocalizationOptions.cs +++ b/src/Localization/Localization/src/LocalizationOptions.cs @@ -8,6 +8,12 @@ namespace Microsoft.Extensions.Localization /// public class LocalizationOptions { + /// + /// Creates a new . + /// + public LocalizationOptions() + { } + /// /// The relative path under application root where resource files are located. /// diff --git a/src/Localization/Localization/src/ResourceNamesCache.cs b/src/Localization/Localization/src/ResourceNamesCache.cs index 86e94c102a..72b7986d9f 100644 --- a/src/Localization/Localization/src/ResourceNamesCache.cs +++ b/src/Localization/Localization/src/ResourceNamesCache.cs @@ -1,5 +1,5 @@ -// 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. +// 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.Collections.Concurrent; @@ -14,6 +14,13 @@ namespace Microsoft.Extensions.Localization { private readonly ConcurrentDictionary> _cache = new ConcurrentDictionary>(); + /// + /// Creates a new + /// + public ResourceNamesCache() + { + } + /// public IList GetOrAdd(string name, Func> valueFactory) { diff --git a/src/ObjectPool/src/DefaultObjectPool.cs b/src/ObjectPool/src/DefaultObjectPool.cs index 070508a014..0ab6ed7608 100644 --- a/src/ObjectPool/src/DefaultObjectPool.cs +++ b/src/ObjectPool/src/DefaultObjectPool.cs @@ -8,6 +8,10 @@ using System.Threading; namespace Microsoft.Extensions.ObjectPool { + /// + /// Default implementation of . + /// + /// The type to pool objects for. public class DefaultObjectPool : ObjectPool where T : class { private protected readonly ObjectWrapper[] _items; @@ -18,11 +22,20 @@ namespace Microsoft.Extensions.ObjectPool // This class was introduced in 2.1 to avoid the interface call where possible private protected readonly PooledObjectPolicy _fastPolicy; + /// + /// Creates an instance of . + /// + /// The pooling policy to use. public DefaultObjectPool(IPooledObjectPolicy policy) : this(policy, Environment.ProcessorCount * 2) { } + /// + /// Creates an instance of . + /// + /// The pooling policy to use. + /// The maximum number of objects to retain in the pool. public DefaultObjectPool(IPooledObjectPolicy policy, int maximumRetained) { _policy = policy ?? throw new ArgumentNullException(nameof(policy)); diff --git a/src/ObjectPool/src/DefaultObjectPoolProvider.cs b/src/ObjectPool/src/DefaultObjectPoolProvider.cs index 2e7767ab35..f5085c26ef 100644 --- a/src/ObjectPool/src/DefaultObjectPoolProvider.cs +++ b/src/ObjectPool/src/DefaultObjectPoolProvider.cs @@ -5,10 +5,17 @@ using System; namespace Microsoft.Extensions.ObjectPool { + /// + /// A provider of 's. + /// public class DefaultObjectPoolProvider : ObjectPoolProvider { + /// + /// The maximum number of objects to retain in the pool. + /// public int MaximumRetained { get; set; } = Environment.ProcessorCount * 2; + /// public override ObjectPool Create(IPooledObjectPolicy policy) { if (policy == null) diff --git a/src/ObjectPool/src/IPooledObjectPolicy.cs b/src/ObjectPool/src/IPooledObjectPolicy.cs index 54611bad30..7bd9b9394a 100644 --- a/src/ObjectPool/src/IPooledObjectPolicy.cs +++ b/src/ObjectPool/src/IPooledObjectPolicy.cs @@ -3,10 +3,23 @@ namespace Microsoft.Extensions.ObjectPool { + /// + /// Represents a policy for managing pooled objects. + /// + /// The type of object which is being pooled. public interface IPooledObjectPolicy { + /// + /// Create a . + /// + /// The which was created. T Create(); + /// + /// Put an object in the pool. + /// + /// The object to put in the pool. + /// true if put in the pool. bool Return(T obj); } } diff --git a/src/ObjectPool/src/ObjectPool.cs b/src/ObjectPool/src/ObjectPool.cs index 691beae60c..f7065c2223 100644 --- a/src/ObjectPool/src/ObjectPool.cs +++ b/src/ObjectPool/src/ObjectPool.cs @@ -3,15 +3,31 @@ namespace Microsoft.Extensions.ObjectPool { + /// + /// A pool of objects. + /// + /// The type of objects to pool. public abstract class ObjectPool where T : class { + /// + /// Returns an object from the pool. + /// + /// An object from the pool. public abstract T Get(); + /// + /// Put an object in the pool. + /// + /// The object to add to the pool. public abstract void Return(T obj); } + /// + /// Methods for creating 's. + /// public static class ObjectPool { + /// public static ObjectPool Create(IPooledObjectPolicy policy = null) where T : class, new() { var provider = new DefaultObjectPoolProvider(); diff --git a/src/ObjectPool/src/ObjectPoolProvider.cs b/src/ObjectPool/src/ObjectPoolProvider.cs index 909795dd35..36db247bfe 100644 --- a/src/ObjectPool/src/ObjectPoolProvider.cs +++ b/src/ObjectPool/src/ObjectPoolProvider.cs @@ -3,13 +3,24 @@ namespace Microsoft.Extensions.ObjectPool { + /// + /// A provider of 's. + /// public abstract class ObjectPoolProvider { + /// + /// Creates an . + /// + /// The type to create a pool for. public ObjectPool Create() where T : class, new() { return Create(new DefaultPooledObjectPolicy()); } + /// + /// Creates an with the given . + /// + /// The type to create a pool for. public abstract ObjectPool Create(IPooledObjectPolicy policy) where T : class; } } From bd0623bf7265048ccd813dbc02cdf731c13923a9 Mon Sep 17 00:00:00 2001 From: Ryan Brandenburg Date: Thu, 27 Jun 2019 16:25:14 -0700 Subject: [PATCH 0113/1101] Fix generic - /// Default implementation of . + /// Default implementation of . /// /// The type to pool objects for. public class DefaultObjectPool : ObjectPool where T : class @@ -23,7 +23,7 @@ namespace Microsoft.Extensions.ObjectPool private protected readonly PooledObjectPolicy _fastPolicy; /// - /// Creates an instance of . + /// Creates an instance of . /// /// The pooling policy to use. public DefaultObjectPool(IPooledObjectPolicy policy) @@ -32,7 +32,7 @@ namespace Microsoft.Extensions.ObjectPool } /// - /// Creates an instance of . + /// Creates an instance of . /// /// The pooling policy to use. /// The maximum number of objects to retain in the pool. diff --git a/src/ObjectPool/src/DefaultObjectPoolProvider.cs b/src/ObjectPool/src/DefaultObjectPoolProvider.cs index f5085c26ef..a264cb65d6 100644 --- a/src/ObjectPool/src/DefaultObjectPoolProvider.cs +++ b/src/ObjectPool/src/DefaultObjectPoolProvider.cs @@ -6,7 +6,7 @@ using System; namespace Microsoft.Extensions.ObjectPool { /// - /// A provider of 's. + /// A provider of 's. /// public class DefaultObjectPoolProvider : ObjectPoolProvider { diff --git a/src/ObjectPool/src/ObjectPool.cs b/src/ObjectPool/src/ObjectPool.cs index f7065c2223..9dfe1ca55a 100644 --- a/src/ObjectPool/src/ObjectPool.cs +++ b/src/ObjectPool/src/ObjectPool.cs @@ -23,7 +23,7 @@ namespace Microsoft.Extensions.ObjectPool } /// - /// Methods for creating 's. + /// Methods for creating 's. /// public static class ObjectPool { diff --git a/src/ObjectPool/src/ObjectPoolProvider.cs b/src/ObjectPool/src/ObjectPoolProvider.cs index 36db247bfe..48be729f7a 100644 --- a/src/ObjectPool/src/ObjectPoolProvider.cs +++ b/src/ObjectPool/src/ObjectPoolProvider.cs @@ -4,7 +4,7 @@ namespace Microsoft.Extensions.ObjectPool { /// - /// A provider of 's. + /// A provider of 's. /// public abstract class ObjectPoolProvider { @@ -18,7 +18,7 @@ namespace Microsoft.Extensions.ObjectPool } /// - /// Creates an with the given . + /// Creates an with the given . /// /// The type to create a pool for. public abstract ObjectPool Create(IPooledObjectPolicy policy) where T : class; From 109d388e5ec64c5e83c0e0c8e6725c7503c57d9f Mon Sep 17 00:00:00 2001 From: Ryan Brandenburg Date: Thu, 27 Jun 2019 16:57:47 -0700 Subject: [PATCH 0114/1101] More doc comments \n\nCommit migrated from https://github.com/dotnet/extensions/commit/d3cad4c7872ea742be8299aa454ab15da6fb5623 --- .../Localization/src/ResourceManagerStringLocalizer.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Localization/Localization/src/ResourceManagerStringLocalizer.cs b/src/Localization/Localization/src/ResourceManagerStringLocalizer.cs index 90f8e077b4..a8321fca0a 100644 --- a/src/Localization/Localization/src/ResourceManagerStringLocalizer.cs +++ b/src/Localization/Localization/src/ResourceManagerStringLocalizer.cs @@ -177,7 +177,7 @@ namespace Microsoft.Extensions.Localization /// /// Returns all strings in the specified culture. /// - /// + /// Whether to include parent cultures in the search for a resource. /// The to get strings for. /// The strings. protected IEnumerable GetAllStrings(bool includeParentCultures, CultureInfo culture) From 25cad453f462c35ae4d4ae3e22c871c2b303562a Mon Sep 17 00:00:00 2001 From: Ryan Brandenburg Date: Fri, 28 Jun 2019 15:59:10 -0700 Subject: [PATCH 0115/1101] More doc comment additions \n\nCommit migrated from https://github.com/dotnet/extensions/commit/551e7c269a9050c3a45bd0b7e24e05d5b90ff122 --- src/ObjectPool/src/ObjectPool.cs | 4 ++-- src/Shared/CommandLineUtils/Utilities/ArgumentEscaper.cs | 6 +++--- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/ObjectPool/src/ObjectPool.cs b/src/ObjectPool/src/ObjectPool.cs index 9dfe1ca55a..bfc495528a 100644 --- a/src/ObjectPool/src/ObjectPool.cs +++ b/src/ObjectPool/src/ObjectPool.cs @@ -10,9 +10,9 @@ namespace Microsoft.Extensions.ObjectPool public abstract class ObjectPool where T : class { /// - /// Returns an object from the pool. + /// Gets an object from the pool is one is available, otherwise creates one. /// - /// An object from the pool. + /// A . public abstract T Get(); /// diff --git a/src/Shared/CommandLineUtils/Utilities/ArgumentEscaper.cs b/src/Shared/CommandLineUtils/Utilities/ArgumentEscaper.cs index 7b696c5175..92543e7f23 100644 --- a/src/Shared/CommandLineUtils/Utilities/ArgumentEscaper.cs +++ b/src/Shared/CommandLineUtils/Utilities/ArgumentEscaper.cs @@ -19,8 +19,8 @@ namespace Microsoft.Extensions.CommandLineUtils /// /// See https://blogs.msdn.microsoft.com/twistylittlepassagesallalike/2011/04/23/everyone-quotes-command-line-arguments-the-wrong-way/ /// - /// - /// + /// The arguments to concatenate. + /// The escaped arguments, concatenated. public static string EscapeAndConcatenate(IEnumerable args) => string.Join(" ", args.Select(EscapeSingleArg)); @@ -104,6 +104,6 @@ namespace Microsoft.Extensions.CommandLineUtils } private static bool ContainsWhitespace(string argument) - => argument.IndexOfAny(new [] { ' ', '\t', '\n' }) >= 0; + => argument.IndexOfAny(new[] { ' ', '\t', '\n' }) >= 0; } } From f8b525f02d84ebb40193d611650d683128bc7acd Mon Sep 17 00:00:00 2001 From: Ryan Brandenburg Date: Mon, 1 Jul 2019 14:24:06 -0700 Subject: [PATCH 0116/1101] PR feedback \n\nCommit migrated from https://github.com/dotnet/extensions/commit/2e1cfcd1f91c260ce1be99a6f11dc1386f0c3e58 --- src/ObjectPool/src/IPooledObjectPolicy.cs | 6 +++--- src/ObjectPool/src/ObjectPool.cs | 4 ++-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/ObjectPool/src/IPooledObjectPolicy.cs b/src/ObjectPool/src/IPooledObjectPolicy.cs index 7bd9b9394a..dc285ca5ef 100644 --- a/src/ObjectPool/src/IPooledObjectPolicy.cs +++ b/src/ObjectPool/src/IPooledObjectPolicy.cs @@ -16,10 +16,10 @@ namespace Microsoft.Extensions.ObjectPool T Create(); /// - /// Put an object in the pool. + /// Return an object to the pool. /// - /// The object to put in the pool. - /// true if put in the pool. + /// The object to return to the pool. + /// true if returned to the pool. bool Return(T obj); } } diff --git a/src/ObjectPool/src/ObjectPool.cs b/src/ObjectPool/src/ObjectPool.cs index bfc495528a..c1b98ae6c0 100644 --- a/src/ObjectPool/src/ObjectPool.cs +++ b/src/ObjectPool/src/ObjectPool.cs @@ -10,13 +10,13 @@ namespace Microsoft.Extensions.ObjectPool public abstract class ObjectPool where T : class { /// - /// Gets an object from the pool is one is available, otherwise creates one. + /// Gets an object from the pool if one is available, otherwise creates one. /// /// A . public abstract T Get(); /// - /// Put an object in the pool. + /// Return an object to the pool. /// /// The object to add to the pool. public abstract void Return(T obj); From 97a039f154890e088520be5fa52660e8c03a28ab Mon Sep 17 00:00:00 2001 From: Ryan Brandenburg Date: Tue, 2 Jul 2019 16:39:42 -0700 Subject: [PATCH 0117/1101] PR feedback \n\nCommit migrated from https://github.com/dotnet/extensions/commit/d656c4f7e22d1c0b84cab1b453c50ce73c89a071 --- .../Abstractions/src/StringLocalizerExtensions.cs | 2 +- src/ObjectPool/src/DefaultObjectPool.cs | 1 + src/ObjectPool/src/DefaultObjectPoolProvider.cs | 2 +- src/ObjectPool/src/IPooledObjectPolicy.cs | 4 ++-- src/ObjectPool/src/ObjectPool.cs | 2 +- src/ObjectPool/src/ObjectPoolProvider.cs | 2 +- 6 files changed, 7 insertions(+), 6 deletions(-) diff --git a/src/Localization/Abstractions/src/StringLocalizerExtensions.cs b/src/Localization/Abstractions/src/StringLocalizerExtensions.cs index 3a57475334..2e19db7c18 100644 --- a/src/Localization/Abstractions/src/StringLocalizerExtensions.cs +++ b/src/Localization/Abstractions/src/StringLocalizerExtensions.cs @@ -7,7 +7,7 @@ using System.Collections.Generic; namespace Microsoft.Extensions.Localization { /// - /// Extension methods for operating on 's. + /// Extension methods for operating on instances. /// public static class StringLocalizerExtensions { diff --git a/src/ObjectPool/src/DefaultObjectPool.cs b/src/ObjectPool/src/DefaultObjectPool.cs index 61cfe365e7..f5627b7898 100644 --- a/src/ObjectPool/src/DefaultObjectPool.cs +++ b/src/ObjectPool/src/DefaultObjectPool.cs @@ -12,6 +12,7 @@ namespace Microsoft.Extensions.ObjectPool /// Default implementation of . /// /// The type to pool objects for. + /// This implementation keeps a cache of retained objects. This means that if objects are returned when the pool has already reached "maximumRetained" objects they will be available to be Garbage Collected. public class DefaultObjectPool : ObjectPool where T : class { private protected readonly ObjectWrapper[] _items; diff --git a/src/ObjectPool/src/DefaultObjectPoolProvider.cs b/src/ObjectPool/src/DefaultObjectPoolProvider.cs index a264cb65d6..b37a946d6d 100644 --- a/src/ObjectPool/src/DefaultObjectPoolProvider.cs +++ b/src/ObjectPool/src/DefaultObjectPoolProvider.cs @@ -6,7 +6,7 @@ using System; namespace Microsoft.Extensions.ObjectPool { /// - /// A provider of 's. + /// The default . /// public class DefaultObjectPoolProvider : ObjectPoolProvider { diff --git a/src/ObjectPool/src/IPooledObjectPolicy.cs b/src/ObjectPool/src/IPooledObjectPolicy.cs index dc285ca5ef..7519398030 100644 --- a/src/ObjectPool/src/IPooledObjectPolicy.cs +++ b/src/ObjectPool/src/IPooledObjectPolicy.cs @@ -16,10 +16,10 @@ namespace Microsoft.Extensions.ObjectPool T Create(); /// - /// Return an object to the pool. + /// Runs some processing when an object was returned to the pool. Can be used to reset the state of an object and indicate if the object should be returned to the pool. /// /// The object to return to the pool. - /// true if returned to the pool. + /// true if the object should be returned to the pool. false if it's not possible/desirable for the pool to keep the object. bool Return(T obj); } } diff --git a/src/ObjectPool/src/ObjectPool.cs b/src/ObjectPool/src/ObjectPool.cs index c1b98ae6c0..0a82ed6f53 100644 --- a/src/ObjectPool/src/ObjectPool.cs +++ b/src/ObjectPool/src/ObjectPool.cs @@ -23,7 +23,7 @@ namespace Microsoft.Extensions.ObjectPool } /// - /// Methods for creating 's. + /// Methods for creating instances. /// public static class ObjectPool { diff --git a/src/ObjectPool/src/ObjectPoolProvider.cs b/src/ObjectPool/src/ObjectPoolProvider.cs index 48be729f7a..6b8ca219ea 100644 --- a/src/ObjectPool/src/ObjectPoolProvider.cs +++ b/src/ObjectPool/src/ObjectPoolProvider.cs @@ -4,7 +4,7 @@ namespace Microsoft.Extensions.ObjectPool { /// - /// A provider of 's. + /// A provider of instances. /// public abstract class ObjectPoolProvider { From 86b55046c96d9c450a31397cac6465c6c9224962 Mon Sep 17 00:00:00 2001 From: Steve Sanderson Date: Tue, 9 Jul 2019 12:40:20 +0100 Subject: [PATCH 0118/1101] In JSInterop calls, use expected value for 'this'. Fixes dotnet/extensions#1477 (dotnet/extensions#1990) \n\nCommit migrated from https://github.com/dotnet/extensions/commit/a664ecb5b3bcf9b2fdeb272e5e2705e8e43eeb9c --- .../src/src/Microsoft.JSInterop.ts | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/src/JSInterop/Microsoft.JSInterop.JS/src/src/Microsoft.JSInterop.ts b/src/JSInterop/Microsoft.JSInterop.JS/src/src/Microsoft.JSInterop.ts index 5386b92bb5..2b3f51300c 100644 --- a/src/JSInterop/Microsoft.JSInterop.JS/src/src/Microsoft.JSInterop.ts +++ b/src/JSInterop/Microsoft.JSInterop.JS/src/src/Microsoft.JSInterop.ts @@ -141,14 +141,6 @@ module DotNet { * Receives incoming calls from .NET and dispatches them to JavaScript. */ export const jsCallDispatcher = { - /** - * Finds the JavaScript function matching the specified identifier. - * - * @param identifier Identifies the globally-reachable function to be returned. - * @returns A Function instance. - */ - findJSFunction, - /** * Invokes the specified synchronous JavaScript function. * @@ -227,8 +219,10 @@ module DotNet { let result: any = window; let resultIdentifier = 'window'; + let lastSegmentValue: any; identifier.split('.').forEach(segment => { if (segment in result) { + lastSegmentValue = result; result = result[segment]; resultIdentifier += '.' + segment; } else { @@ -237,6 +231,8 @@ module DotNet { }); if (result instanceof Function) { + result = result.bind(lastSegmentValue); + cachedJSFunctions[identifier] = result; return result; } else { throw new Error(`The value '${resultIdentifier}' is not a function.`); From 3c42f4436f5645359d37f95394283ff708775558 Mon Sep 17 00:00:00 2001 From: Steve Sanderson Date: Wed, 10 Jul 2019 12:12:11 +0100 Subject: [PATCH 0119/1101] Bring back required export jsCallDispatcher.findJSFunction (dotnet/extensions#2002) \n\nCommit migrated from https://github.com/dotnet/extensions/commit/5350efd2f4faed6c2e2141176e3493d78e0ac27e --- .../Microsoft.JSInterop.JS/src/src/Microsoft.JSInterop.ts | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/JSInterop/Microsoft.JSInterop.JS/src/src/Microsoft.JSInterop.ts b/src/JSInterop/Microsoft.JSInterop.JS/src/src/Microsoft.JSInterop.ts index 2b3f51300c..27d22b0536 100644 --- a/src/JSInterop/Microsoft.JSInterop.JS/src/src/Microsoft.JSInterop.ts +++ b/src/JSInterop/Microsoft.JSInterop.JS/src/src/Microsoft.JSInterop.ts @@ -141,6 +141,14 @@ module DotNet { * Receives incoming calls from .NET and dispatches them to JavaScript. */ export const jsCallDispatcher = { + /** + * Finds the JavaScript function matching the specified identifier. + * + * @param identifier Identifies the globally-reachable function to be returned. + * @returns A Function instance. + */ + findJSFunction, // Note that this is used by the JS interop code inside Mono WebAssembly itself + /** * Invokes the specified synchronous JavaScript function. * From d0a69f986069011ebab0d1d757ce326d2fd22743 Mon Sep 17 00:00:00 2001 From: Pranav K Date: Wed, 10 Jul 2019 07:54:15 -0700 Subject: [PATCH 0120/1101] Json Options (dotnet/extensions#1993) \n\nCommit migrated from https://github.com/dotnet/extensions/commit/912ab186945c8622e2446eb041c786e99c92bd7e --- .../Microsoft.JSInterop/src/JsonSerializerOptionsProvider.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/JSInterop/Microsoft.JSInterop/src/JsonSerializerOptionsProvider.cs b/src/JSInterop/Microsoft.JSInterop/src/JsonSerializerOptionsProvider.cs index fb299aaf02..62244270e3 100644 --- a/src/JSInterop/Microsoft.JSInterop/src/JsonSerializerOptionsProvider.cs +++ b/src/JSInterop/Microsoft.JSInterop/src/JsonSerializerOptionsProvider.cs @@ -9,7 +9,9 @@ namespace Microsoft.JSInterop { public static readonly JsonSerializerOptions Options = new JsonSerializerOptions { + MaxDepth = 32, PropertyNamingPolicy = JsonNamingPolicy.CamelCase, + PropertyNameCaseInsensitive = true, }; } } From 0ae689c8a8b43d8b1e61ee652f4b0505a9f43730 Mon Sep 17 00:00:00 2001 From: Daniel Gasparotto Date: Fri, 12 Jul 2019 23:27:28 +0200 Subject: [PATCH 0121/1101] Handle chunks containing multiple EOL (#6146) Credit to @EEParker https://github.com/EEParker/aspnetcore-vueclimiddleware/commit/a169dd6d39912d7e6ecbb13e5fa349860c26af0b#diff-6d394759c0a824a137b47986d0cce571R158 Co-Authored-By: Jeff Parker, PE --- .../src/Util/EventedStreamReader.cs | 22 ++++++++++++------- 1 file changed, 14 insertions(+), 8 deletions(-) diff --git a/src/Middleware/SpaServices.Extensions/src/Util/EventedStreamReader.cs b/src/Middleware/SpaServices.Extensions/src/Util/EventedStreamReader.cs index 95e018a590..298d5289d6 100644 --- a/src/Middleware/SpaServices.Extensions/src/Util/EventedStreamReader.cs +++ b/src/Middleware/SpaServices.Extensions/src/Util/EventedStreamReader.cs @@ -89,17 +89,23 @@ namespace Microsoft.AspNetCore.NodeServices.Util OnChunk(new ArraySegment(buf, 0, chunkLength)); - var lineBreakPos = Array.IndexOf(buf, '\n', 0, chunkLength); - if (lineBreakPos < 0) + int lineBreakPos = -1; + int startPos = 0; + + // get all the newlines + while ((lineBreakPos = Array.IndexOf(buf, '\n', startPos, chunkLength - startPos)) >= 0 && startPos < chunkLength) { - _linesBuffer.Append(buf, 0, chunkLength); - } - else - { - _linesBuffer.Append(buf, 0, lineBreakPos + 1); + var length = (lineBreakPos + 1) - startPos; + _linesBuffer.Append(buf, startPos, length); OnCompleteLine(_linesBuffer.ToString()); _linesBuffer.Clear(); - _linesBuffer.Append(buf, lineBreakPos + 1, chunkLength - (lineBreakPos + 1)); + startPos = lineBreakPos + 1; + } + + // get the rest + if (lineBreakPos < 0 && startPos < chunkLength) + { + _linesBuffer.Append(buf, startPos, chunkLength); } } } From 43c5fde21bf1623e1ddd8e8aca141a2c45bbb258 Mon Sep 17 00:00:00 2001 From: Isaac Levin <8878502+isaac2004@users.noreply.github.com> Date: Fri, 12 Jul 2019 19:25:25 -0400 Subject: [PATCH 0122/1101] Add files via upload --- .../BlazorServerWeb-CSharp/Pages/Error.razor | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) create mode 100644 src/ProjectTemplates/Web.ProjectTemplates/content/BlazorServerWeb-CSharp/Pages/Error.razor diff --git a/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorServerWeb-CSharp/Pages/Error.razor b/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorServerWeb-CSharp/Pages/Error.razor new file mode 100644 index 0000000000..cd87ea9518 --- /dev/null +++ b/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorServerWeb-CSharp/Pages/Error.razor @@ -0,0 +1,16 @@ +@page "/error" + + +

Error.

+

An error occurred while processing your request.

+ +

Development Mode

+

+ Swapping to Development environment will display more detailed information about the error that occurred. +

+

+ The Development environment shouldn't be enabled for deployed applications. + It can result in displaying sensitive information from exceptions to end users. + For local debugging, enable the Development environment by setting the ASPNETCORE_ENVIRONMENT environment variable to Development + and restarting the app. +

\ No newline at end of file From 1e1f9f1c22fa327d7d844828a40b997d50456183 Mon Sep 17 00:00:00 2001 From: Isaac Levin Date: Tue, 16 Jul 2019 11:39:56 -0400 Subject: [PATCH 0123/1101] added baseline --- src/ProjectTemplates/test/template-baselines.json | 1 + 1 file changed, 1 insertion(+) diff --git a/src/ProjectTemplates/test/template-baselines.json b/src/ProjectTemplates/test/template-baselines.json index 39a8c5ddd8..3a1d094890 100644 --- a/src/ProjectTemplates/test/template-baselines.json +++ b/src/ProjectTemplates/test/template-baselines.json @@ -902,6 +902,7 @@ "Data/Migrations/00000000000000_CreateIdentitySchema.Designer.cs", "Data/Migrations/ApplicationDbContextModelSnapshot.cs", "Pages/Counter.razor", + "Pages/Error.razor", "Pages/FetchData.razor", "Pages/Index.razor", "Pages/_Host.cshtml", From 4c0a9d9a6bd36e8e2b0d2764b82c647b444b6303 Mon Sep 17 00:00:00 2001 From: Andrew Stanton-Nurse Date: Tue, 16 Jul 2019 14:42:37 -0700 Subject: [PATCH 0124/1101] update version of SqlClient used by sql cache (dotnet/extensions#2006) \n\nCommit migrated from https://github.com/dotnet/extensions/commit/b1ff0d9dc87b3ad6c555d6abe6ac57e12a673836 --- ...vironmentVariableSkipConditionAttribute.cs | 10 ++--- .../EnvironmentVariableSkipConditionTest.cs | 41 +++++++++++-------- 2 files changed, 29 insertions(+), 22 deletions(-) diff --git a/src/Testing/src/xunit/EnvironmentVariableSkipConditionAttribute.cs b/src/Testing/src/xunit/EnvironmentVariableSkipConditionAttribute.cs index 8bf1bfd15e..fe215a8e0b 100644 --- a/src/Testing/src/xunit/EnvironmentVariableSkipConditionAttribute.cs +++ b/src/Testing/src/xunit/EnvironmentVariableSkipConditionAttribute.cs @@ -1,4 +1,4 @@ -// Copyright (c) .NET Foundation. All rights reserved. +// 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; @@ -52,9 +52,9 @@ namespace Microsoft.AspNetCore.Testing.xunit } /// - /// Skips the test only if the value of the variable matches any of the supplied values. Default is True. + /// Runs the test only if the value of the variable matches any of the supplied values. Default is True. /// - public bool SkipOnMatch { get; set; } = true; + public bool RunOnMatch { get; set; } = true; public bool IsMet { @@ -63,7 +63,7 @@ namespace Microsoft.AspNetCore.Testing.xunit _currentValue = _environmentVariable.Get(_variableName); var hasMatched = _values.Any(value => string.Compare(value, _currentValue, ignoreCase: true) == 0); - if (SkipOnMatch) + if (RunOnMatch) { return hasMatched; } @@ -80,7 +80,7 @@ namespace Microsoft.AspNetCore.Testing.xunit { var value = _currentValue == null ? "(null)" : _currentValue; return $"Test skipped on environment variable with name '{_variableName}' and value '{value}' " + - $"for the '{nameof(SkipOnMatch)}' value of '{SkipOnMatch}'."; + $"for the '{nameof(RunOnMatch)}' value of '{RunOnMatch}'."; } } diff --git a/src/Testing/test/EnvironmentVariableSkipConditionTest.cs b/src/Testing/test/EnvironmentVariableSkipConditionTest.cs index b536ae56f7..d5e7b6342b 100644 --- a/src/Testing/test/EnvironmentVariableSkipConditionTest.cs +++ b/src/Testing/test/EnvironmentVariableSkipConditionTest.cs @@ -1,4 +1,4 @@ -// Copyright (c) .NET Foundation. All rights reserved. +// 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 Xunit; @@ -8,7 +8,7 @@ namespace Microsoft.AspNetCore.Testing.xunit public class EnvironmentVariableSkipConditionTest { private readonly string _skipReason = "Test skipped on environment variable with name '{0}' and value '{1}'" + - $" for the '{nameof(EnvironmentVariableSkipConditionAttribute.SkipOnMatch)}' value of '{{2}}'."; + $" for the '{nameof(EnvironmentVariableSkipConditionAttribute.RunOnMatch)}' value of '{{2}}'."; [Theory] [InlineData("false")] @@ -18,7 +18,7 @@ namespace Microsoft.AspNetCore.Testing.xunit { // Arrange var attribute = new EnvironmentVariableSkipConditionAttribute( - new TestEnvironmentVariable(environmentVariableValue), + new TestEnvironmentVariable("Run", environmentVariableValue), "Run", "true"); @@ -37,7 +37,7 @@ namespace Microsoft.AspNetCore.Testing.xunit { // Arrange var attribute = new EnvironmentVariableSkipConditionAttribute( - new TestEnvironmentVariable(environmentVariableValue), + new TestEnvironmentVariable("Run", environmentVariableValue), "Run", "true"); @@ -47,7 +47,7 @@ namespace Microsoft.AspNetCore.Testing.xunit // Assert Assert.True(isMet); Assert.Equal( - string.Format(_skipReason, "Run", environmentVariableValue, attribute.SkipOnMatch), + string.Format(_skipReason, "Run", environmentVariableValue, attribute.RunOnMatch), attribute.SkipReason); } @@ -56,7 +56,7 @@ namespace Microsoft.AspNetCore.Testing.xunit { // Arrange var attribute = new EnvironmentVariableSkipConditionAttribute( - new TestEnvironmentVariable(null), + new TestEnvironmentVariable("Run", null), "Run", "true", null); // skip the test when the variable 'Run' is explicitly set to 'true' or is null (default) @@ -66,7 +66,7 @@ namespace Microsoft.AspNetCore.Testing.xunit // Assert Assert.True(isMet); Assert.Equal( - string.Format(_skipReason, "Run", "(null)", attribute.SkipOnMatch), + string.Format(_skipReason, "Run", "(null)", attribute.RunOnMatch), attribute.SkipReason); } @@ -78,7 +78,7 @@ namespace Microsoft.AspNetCore.Testing.xunit { // Arrange var attribute = new EnvironmentVariableSkipConditionAttribute( - new TestEnvironmentVariable(environmentVariableValue), + new TestEnvironmentVariable("Run", environmentVariableValue), "Run", "false", "", null); @@ -94,7 +94,7 @@ namespace Microsoft.AspNetCore.Testing.xunit { // Arrange var attribute = new EnvironmentVariableSkipConditionAttribute( - new TestEnvironmentVariable("100"), + new TestEnvironmentVariable("Build", "100"), "Build", "125", "126"); @@ -109,16 +109,16 @@ namespace Microsoft.AspNetCore.Testing.xunit [InlineData("CentOS")] [InlineData(null)] [InlineData("")] - public void IsMet_Matches_WhenSkipOnMatchIsFalse(string environmentVariableValue) + public void IsMet_Matches_WhenRunOnMatchIsFalse(string environmentVariableValue) { // Arrange var attribute = new EnvironmentVariableSkipConditionAttribute( - new TestEnvironmentVariable(environmentVariableValue), + new TestEnvironmentVariable("LinuxFlavor", environmentVariableValue), "LinuxFlavor", "Ubuntu14.04") { // Example: Run this test on all OSes except on "Ubuntu14.04" - SkipOnMatch = false + RunOnMatch = false }; // Act @@ -129,16 +129,16 @@ namespace Microsoft.AspNetCore.Testing.xunit } [Fact] - public void IsMet_DoesNotMatch_WhenSkipOnMatchIsFalse() + public void IsMet_DoesNotMatch_WhenRunOnMatchIsFalse() { // Arrange var attribute = new EnvironmentVariableSkipConditionAttribute( - new TestEnvironmentVariable("Ubuntu14.04"), + new TestEnvironmentVariable("LinuxFlavor", "Ubuntu14.04"), "LinuxFlavor", "Ubuntu14.04") { // Example: Run this test on all OSes except on "Ubuntu14.04" - SkipOnMatch = false + RunOnMatch = false }; // Act @@ -150,8 +150,11 @@ namespace Microsoft.AspNetCore.Testing.xunit private struct TestEnvironmentVariable : IEnvironmentVariable { - public TestEnvironmentVariable(string value) + private readonly string _varName; + + public TestEnvironmentVariable(string varName, string value) { + _varName = varName; Value = value; } @@ -159,7 +162,11 @@ namespace Microsoft.AspNetCore.Testing.xunit public string Get(string name) { - return Value; + if(string.Equals(name, _varName, System.StringComparison.OrdinalIgnoreCase)) + { + return Value; + } + return string.Empty; } } } From b4bcb1fd15d19eb008079bd4344edc05d920e51b Mon Sep 17 00:00:00 2001 From: Javier Calvarro Nelson Date: Wed, 17 Jul 2019 17:12:37 +0200 Subject: [PATCH 0125/1101] [JSInterop] Updates the JSInterop abstractions to better support logging and diagnostics. (dotnet/extensions#2043) * Add an overload to handle .NET completion calls without going through JS interop. * Add endInvokeHook on the client-side. * Replace OnDotNetInvocationException with EndInvokeDotNet.\n\nCommit migrated from https://github.com/dotnet/extensions/commit/4312b9a050700833e01a01cf9381ff9033660bc1 --- .../src/src/Microsoft.JSInterop.ts | 25 ++++--- .../ref/Microsoft.JSInterop.netstandard2.0.cs | 13 +--- .../src/DotNetDispatcher.cs | 41 ++++++++--- .../src/JSAsyncCallResult.cs | 6 +- .../Microsoft.JSInterop/src/JSRuntimeBase.cs | 45 ++++-------- .../test/DotNetDispatcherTest.cs | 71 +++++++++++-------- .../test/DotNetObjectRefTest.cs | 5 ++ .../test/JSInProcessRuntimeBaseTest.cs | 3 + .../test/JSRuntimeBaseTest.cs | 36 +++++++--- ...Mono.WebAssembly.Interop.netstandard2.0.cs | 1 + .../src/MonoWebAssemblyJSRuntime.cs | 33 +++++++++ 11 files changed, 173 insertions(+), 106 deletions(-) diff --git a/src/JSInterop/Microsoft.JSInterop.JS/src/src/Microsoft.JSInterop.ts b/src/JSInterop/Microsoft.JSInterop.JS/src/src/Microsoft.JSInterop.ts index 27d22b0536..60f6f800a6 100644 --- a/src/JSInterop/Microsoft.JSInterop.JS/src/src/Microsoft.JSInterop.ts +++ b/src/JSInterop/Microsoft.JSInterop.JS/src/src/Microsoft.JSInterop.ts @@ -75,7 +75,7 @@ module DotNet { try { const argsJson = JSON.stringify(args, argReplacer); getRequiredDispatcher().beginInvokeDotNetFromJS(asyncCallId, assemblyName, methodIdentifier, dotNetObjectId, argsJson); - } catch(ex) { + } catch (ex) { // Synchronous failure completePendingCall(asyncCallId, false, ex); } @@ -116,7 +116,7 @@ module DotNet { export interface DotNetCallDispatcher { /** * Optional. If implemented, invoked by the runtime to perform a synchronous call to a .NET method. - * + * * @param assemblyName The short name (without key/version or .dll extension) of the .NET assembly holding the method to invoke. The value may be null when invoking instance methods. * @param methodIdentifier The identifier of the method to invoke. The method must have a [JSInvokable] attribute specifying this identifier. * @param dotNetObjectId If given, the call will be to an instance method on the specified DotNetObject. Pass null or undefined to call static methods. @@ -135,6 +135,15 @@ module DotNet { * @param argsJson JSON representation of arguments to pass to the method. */ beginInvokeDotNetFromJS(callId: number, assemblyName: string | null, methodIdentifier: string, dotNetObjectId: number | null, argsJson: string): void; + + /** + * Invoked by the runtime to complete an asynchronous JavaScript function call started from .NET + * + * @param callId A value identifying the asynchronous operation. + * @param succeded Whether the operation succeeded or not. + * @param resultOrError The serialized result or the serialized error from the async operation. + */ + endInvokeJSFromDotNet(callId: number, succeeded: boolean, resultOrError: any): void; } /** @@ -183,8 +192,8 @@ module DotNet { // On completion, dispatch result back to .NET // Not using "await" because it codegens a lot of boilerplate promise.then( - result => getRequiredDispatcher().beginInvokeDotNetFromJS(0, 'Microsoft.JSInterop', 'DotNetDispatcher.EndInvoke', null, JSON.stringify([asyncHandle, true, result], argReplacer)), - error => getRequiredDispatcher().beginInvokeDotNetFromJS(0, 'Microsoft.JSInterop', 'DotNetDispatcher.EndInvoke', null, JSON.stringify([asyncHandle, false, formatError(error)])) + result => getRequiredDispatcher().endInvokeJSFromDotNet(asyncHandle, true, JSON.stringify([asyncHandle, true, result], argReplacer)), + error => getRequiredDispatcher().endInvokeJSFromDotNet(asyncHandle, false, JSON.stringify([asyncHandle, false, formatError(error)])) ); } }, @@ -219,7 +228,7 @@ module DotNet { return error ? error.toString() : 'null'; } } - + function findJSFunction(identifier: string): Function { if (cachedJSFunctions.hasOwnProperty(identifier)) { return cachedJSFunctions[identifier]; @@ -247,7 +256,7 @@ module DotNet { } } - class DotNetObject { + class DotNetObject { constructor(private _id: number) { } @@ -268,14 +277,14 @@ module DotNet { } public serializeAsArg() { - return {__dotNetObject: this._id}; + return { __dotNetObject: this._id }; } } const dotNetObjectRefKey = '__dotNetObject'; attachReviver(function reviveDotNetObject(key: any, value: any) { if (value && typeof value === 'object' && value.hasOwnProperty(dotNetObjectRefKey)) { - return new DotNetObject(value.__dotNetObject); + return new DotNetObject(value.__dotNetObject); } // Unrecognized - let another reviver handle it diff --git a/src/JSInterop/Microsoft.JSInterop/ref/Microsoft.JSInterop.netstandard2.0.cs b/src/JSInterop/Microsoft.JSInterop/ref/Microsoft.JSInterop.netstandard2.0.cs index ad95b7342b..125a555c9d 100644 --- a/src/JSInterop/Microsoft.JSInterop/ref/Microsoft.JSInterop.netstandard2.0.cs +++ b/src/JSInterop/Microsoft.JSInterop/ref/Microsoft.JSInterop.netstandard2.0.cs @@ -6,8 +6,7 @@ namespace Microsoft.JSInterop public static partial class DotNetDispatcher { public static void BeginInvoke(string callId, string assemblyName, string methodIdentifier, long dotNetObjectId, string argsJson) { } - [Microsoft.JSInterop.JSInvokableAttribute("DotNetDispatcher.EndInvoke")] - public static void EndInvoke(long asyncHandle, bool succeeded, Microsoft.JSInterop.Internal.JSAsyncCallResult result) { } + public static void EndInvoke(string arguments) { } public static string Invoke(string assemblyName, string methodIdentifier, long dotNetObjectId, string argsJson) { throw null; } [Microsoft.JSInterop.JSInvokableAttribute("DotNetDispatcher.ReleaseDotNetObject")] public static void ReleaseDotNetObject(long dotNetObjectId) { } @@ -62,16 +61,8 @@ namespace Microsoft.JSInterop protected JSRuntimeBase() { } protected System.TimeSpan? DefaultAsyncTimeout { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } protected abstract void BeginInvokeJS(long taskId, string identifier, string argsJson); + protected internal abstract void EndInvokeDotNet(string callId, bool success, object resultOrError, string assemblyName, string methodIdentifier, long dotNetObjectId); public System.Threading.Tasks.Task InvokeAsync(string identifier, System.Collections.Generic.IEnumerable args, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) { throw null; } public System.Threading.Tasks.Task InvokeAsync(string identifier, params object[] args) { throw null; } - protected virtual object OnDotNetInvocationException(System.Exception exception, string assemblyName, string methodIdentifier) { throw null; } - } -} -namespace Microsoft.JSInterop.Internal -{ - [System.ComponentModel.EditorBrowsableAttribute(System.ComponentModel.EditorBrowsableState.Never)] - public sealed partial class JSAsyncCallResult - { - internal JSAsyncCallResult() { } } } diff --git a/src/JSInterop/Microsoft.JSInterop/src/DotNetDispatcher.cs b/src/JSInterop/Microsoft.JSInterop/src/DotNetDispatcher.cs index 952a6061ab..60b6ad9ab0 100644 --- a/src/JSInterop/Microsoft.JSInterop/src/DotNetDispatcher.cs +++ b/src/JSInterop/Microsoft.JSInterop/src/DotNetDispatcher.cs @@ -8,9 +8,7 @@ using System.Linq; using System.Reflection; using System.Runtime.ExceptionServices; using System.Text.Json; -using System.Text.Json.Serialization; using System.Threading.Tasks; -using Microsoft.JSInterop.Internal; namespace Microsoft.JSInterop { @@ -20,6 +18,7 @@ namespace Microsoft.JSInterop public static class DotNetDispatcher { internal const string DotNetObjectRefKey = nameof(DotNetObjectRef.__dotNetObject); + private static readonly Type[] EndInvokeParameterTypes = new Type[] { typeof(long), typeof(bool), typeof(JSAsyncCallResult) }; private static readonly ConcurrentDictionary> _cachedMethodsByAssembly = new ConcurrentDictionary>(); @@ -104,7 +103,7 @@ namespace Microsoft.JSInterop else if (syncException != null) { // Threw synchronously, let's respond. - jsRuntimeBaseInstance.EndInvokeDotNet(callId, false, syncException, assemblyName, methodIdentifier); + jsRuntimeBaseInstance.EndInvokeDotNet(callId, false, syncException, assemblyName, methodIdentifier, dotNetObjectId); } else if (syncResult is Task task) { @@ -116,16 +115,16 @@ namespace Microsoft.JSInterop { var exception = t.Exception.GetBaseException(); - jsRuntimeBaseInstance.EndInvokeDotNet(callId, false, ExceptionDispatchInfo.Capture(exception), assemblyName, methodIdentifier); + jsRuntimeBaseInstance.EndInvokeDotNet(callId, false, ExceptionDispatchInfo.Capture(exception), assemblyName, methodIdentifier, dotNetObjectId); } var result = TaskGenericsUtil.GetTaskResult(task); - jsRuntimeBaseInstance.EndInvokeDotNet(callId, true, result, assemblyName, methodIdentifier); + jsRuntimeBaseInstance.EndInvokeDotNet(callId, true, result, assemblyName, methodIdentifier, dotNetObjectId); }, TaskScheduler.Current); } else { - jsRuntimeBaseInstance.EndInvokeDotNet(callId, true, syncResult, assemblyName, methodIdentifier); + jsRuntimeBaseInstance.EndInvokeDotNet(callId, true, syncResult, assemblyName, methodIdentifier, dotNetObjectId); } } @@ -248,11 +247,31 @@ namespace Microsoft.JSInterop /// Receives notification that a call from .NET to JS has finished, marking the /// associated as completed. /// - /// The identifier for the function invocation. - /// A flag to indicate whether the invocation succeeded. - /// If is true, specifies the invocation result. If is false, gives the corresponding to the invocation failure. - [JSInvokable(nameof(DotNetDispatcher) + "." + nameof(EndInvoke))] - public static void EndInvoke(long asyncHandle, bool succeeded, JSAsyncCallResult result) + /// + /// All exceptions from are caught + /// are delivered via JS interop to the JavaScript side when it requests confirmation, as + /// the mechanism to call relies on + /// using JS->.NET interop. This overload is meant for directly triggering completion callbacks + /// for .NET -> JS operations without going through JS interop, so the callsite for this + /// method is responsible for handling any possible exception generated from the arguments + /// passed in as parameters. + /// + /// The serialized arguments for the callback completion. + /// + /// This method can throw any exception either from the argument received or as a result + /// of executing any callback synchronously upon completion. + /// + public static void EndInvoke(string arguments) + { + var parsedArgs = ParseArguments( + nameof(EndInvoke), + arguments, + EndInvokeParameterTypes); + + EndInvoke((long)parsedArgs[0], (bool)parsedArgs[1], (JSAsyncCallResult)parsedArgs[2]); + } + + private static void EndInvoke(long asyncHandle, bool succeeded, JSAsyncCallResult result) => ((JSRuntimeBase)JSRuntime.Current).EndInvokeJS(asyncHandle, succeeded, result); /// diff --git a/src/JSInterop/Microsoft.JSInterop/src/JSAsyncCallResult.cs b/src/JSInterop/Microsoft.JSInterop/src/JSAsyncCallResult.cs index 1ea8c47995..209438529b 100644 --- a/src/JSInterop/Microsoft.JSInterop/src/JSAsyncCallResult.cs +++ b/src/JSInterop/Microsoft.JSInterop/src/JSAsyncCallResult.cs @@ -1,10 +1,9 @@ // 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.ComponentModel; using System.Text.Json; -namespace Microsoft.JSInterop.Internal +namespace Microsoft.JSInterop { // This type takes care of a special case in handling the result of an async call from // .NET to JS. The information about what type the result should be exists only on the @@ -23,8 +22,7 @@ namespace Microsoft.JSInterop.Internal /// /// Intended for framework use only. /// - [EditorBrowsable(EditorBrowsableState.Never)] - public sealed class JSAsyncCallResult + internal sealed class JSAsyncCallResult { internal JSAsyncCallResult(JsonDocument document, JsonElement jsonElement) { diff --git a/src/JSInterop/Microsoft.JSInterop/src/JSRuntimeBase.cs b/src/JSInterop/Microsoft.JSInterop/src/JSRuntimeBase.cs index 562a6a27ba..13a9ccae1c 100644 --- a/src/JSInterop/Microsoft.JSInterop/src/JSRuntimeBase.cs +++ b/src/JSInterop/Microsoft.JSInterop/src/JSRuntimeBase.cs @@ -9,7 +9,6 @@ using System.Runtime.ExceptionServices; using System.Text.Json; using System.Threading; using System.Threading.Tasks; -using Microsoft.JSInterop.Internal; namespace Microsoft.JSInterop { @@ -124,37 +123,21 @@ namespace Microsoft.JSInterop protected abstract void BeginInvokeJS(long taskId, string identifier, string argsJson); /// - /// Allows derived classes to configure the information about an exception in a JS interop call that gets sent to JavaScript. + /// Completes an async JS interop call from JavaScript to .NET /// - /// - /// This callback can be used in remote JS interop scenarios to sanitize exceptions that happen on the server to avoid disclosing - /// sensitive information to remote browser clients. - /// - /// The exception that occurred. - /// The assembly for the invoked .NET method. - /// The identifier for the invoked .NET method. - /// An object containing information about the exception. - protected virtual object OnDotNetInvocationException(Exception exception, string assemblyName, string methodIdentifier) => exception.ToString(); - - internal void EndInvokeDotNet(string callId, bool success, object resultOrException, string assemblyName, string methodIdentifier) - { - // For failures, the common case is to call EndInvokeDotNet with the Exception object. - // For these we'll serialize as something that's useful to receive on the JS side. - // If the value is not an Exception, we'll just rely on it being directly JSON-serializable. - if (!success && resultOrException is Exception ex) - { - resultOrException = OnDotNetInvocationException(ex, assemblyName, methodIdentifier); - } - else if (!success && resultOrException is ExceptionDispatchInfo edi) - { - resultOrException = OnDotNetInvocationException(edi.SourceException, assemblyName, methodIdentifier); - } - - // We pass 0 as the async handle because we don't want the JS-side code to - // send back any notification (we're just providing a result for an existing async call) - var args = JsonSerializer.Serialize(new[] { callId, success, resultOrException }, JsonSerializerOptionsProvider.Options); - BeginInvokeJS(0, "DotNet.jsCallDispatcher.endInvokeDotNetFromJS", args); - } + /// The id of the JavaScript callback to execute on completion. + /// Whether the operation succeeded or not. + /// The result of the operation or an object containing error details. + /// The name of the method assembly if the invocation was for a static method. + /// The identifier for the method within the assembly. + /// The tracking id of the dotnet object if the invocation was for an instance method. + protected internal abstract void EndInvokeDotNet( + string callId, + bool success, + object resultOrError, + string assemblyName, + string methodIdentifier, + long dotNetObjectId); internal void EndInvokeJS(long taskId, bool succeeded, JSAsyncCallResult asyncCallResult) { diff --git a/src/JSInterop/Microsoft.JSInterop/test/DotNetDispatcherTest.cs b/src/JSInterop/Microsoft.JSInterop/test/DotNetDispatcherTest.cs index 9e8a7b672e..a4a0de5a3f 100644 --- a/src/JSInterop/Microsoft.JSInterop/test/DotNetDispatcherTest.cs +++ b/src/JSInterop/Microsoft.JSInterop/test/DotNetDispatcherTest.cs @@ -2,7 +2,9 @@ // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; +using System.ComponentModel.DataAnnotations; using System.Linq; +using System.Runtime.ExceptionServices; using System.Text.Json; using System.Text.Json.Serialization; using System.Threading.Tasks; @@ -295,23 +297,18 @@ namespace Microsoft.JSInterop.Tests var resultTask = jsRuntime.NextInvocationTask; DotNetDispatcher.BeginInvoke(callId, null, "InvokableAsyncMethod", 1, argsJson); await resultTask; - var result = JsonDocument.Parse(jsRuntime.LastInvocationArgsJson).RootElement; - var resultValue = result[2]; - // Assert: Correct info to complete the async call - Assert.Equal(0, jsRuntime.LastInvocationAsyncHandle); // 0 because it doesn't want a further callback from JS to .NET - Assert.Equal("DotNet.jsCallDispatcher.endInvokeDotNetFromJS", jsRuntime.LastInvocationIdentifier); - Assert.Equal(3, result.GetArrayLength()); - Assert.Equal(callId, result[0].GetString()); - Assert.True(result[1].GetBoolean()); // Success flag + // Assert: Correct completion information + Assert.Equal(callId, jsRuntime.LastCompletionCallId); + Assert.True(jsRuntime.LastCompletionStatus); + var result = Assert.IsType(jsRuntime.LastCompletionResult); + var resultDto1 = Assert.IsType(result[0]); - // Assert: First result value marshalled via JSON - var resultDto1 = JsonSerializer.Deserialize(resultValue[0].GetRawText(), JsonSerializerOptionsProvider.Options); Assert.Equal("STRING VIA JSON", resultDto1.StringVal); Assert.Equal(2000, resultDto1.IntVal); // Assert: Second result value marshalled by ref - var resultDto2Ref = JsonSerializer.Deserialize>(resultValue[1].GetRawText(), JsonSerializerOptionsProvider.Options); + var resultDto2Ref = Assert.IsType>(result[1]); var resultDto2 = resultDto2Ref.Value; Assert.Equal("MY STRING", resultDto2.StringVal); Assert.Equal(2468, resultDto2.IntVal); @@ -330,13 +327,12 @@ namespace Microsoft.JSInterop.Tests await resultTask; // This won't throw, it sets properties on the jsRuntime. // Assert - var result = JsonDocument.Parse(jsRuntime.LastInvocationArgsJson).RootElement; - Assert.Equal(callId, result[0].GetString()); - Assert.False(result[1].GetBoolean()); // Fails + Assert.Equal(callId, jsRuntime.LastCompletionCallId); + Assert.False(jsRuntime.LastCompletionStatus); // Fails // Make sure the method that threw the exception shows up in the call stack // https://github.com/aspnet/AspNetCore/issues/8612 - var exception = result[2].GetString(); + var exception = jsRuntime.LastCompletionResult is ExceptionDispatchInfo edi ? edi.SourceException.ToString() : null; Assert.Contains(nameof(ThrowingClass.ThrowingMethod), exception); }); @@ -353,13 +349,12 @@ namespace Microsoft.JSInterop.Tests await resultTask; // This won't throw, it sets properties on the jsRuntime. // Assert - var result = JsonDocument.Parse(jsRuntime.LastInvocationArgsJson).RootElement; - Assert.Equal(callId, result[0].GetString()); - Assert.False(result[1].GetBoolean()); // Fails + Assert.Equal(callId, jsRuntime.LastCompletionCallId); + Assert.False(jsRuntime.LastCompletionStatus); // Fails // Make sure the method that threw the exception shows up in the call stack // https://github.com/aspnet/AspNetCore/issues/8612 - var exception = result[2].GetString(); + var exception = jsRuntime.LastCompletionResult is ExceptionDispatchInfo edi ? edi.SourceException.ToString() : null; Assert.Contains(nameof(ThrowingClass.AsyncThrowingMethod), exception); }); @@ -374,11 +369,10 @@ namespace Microsoft.JSInterop.Tests await resultTask; // This won't throw, it sets properties on the jsRuntime. // Assert - using var jsonDocument = JsonDocument.Parse(jsRuntime.LastInvocationArgsJson); - var result = jsonDocument.RootElement; - Assert.Equal(callId, result[0].GetString()); - Assert.False(result[1].GetBoolean()); // Fails - Assert.Contains("JsonReaderException: '<' is an invalid start of a value.", result[2].GetString()); + Assert.Equal(callId, jsRuntime.LastCompletionCallId); + Assert.False(jsRuntime.LastCompletionStatus); // Fails + var result = Assert.IsType(jsRuntime.LastCompletionResult); + Assert.Contains("JsonReaderException: '<' is an invalid start of a value.", result.SourceException.ToString()); }); [Fact] @@ -390,12 +384,10 @@ namespace Microsoft.JSInterop.Tests DotNetDispatcher.BeginInvoke(callId, null, "InvokableInstanceVoid", 1, null); // Assert - using var jsonDocument = JsonDocument.Parse(jsRuntime.LastInvocationArgsJson); - var result = jsonDocument.RootElement; - Assert.Equal(callId, result[0].GetString()); - Assert.False(result[1].GetBoolean()); // Fails - - Assert.StartsWith("System.ArgumentException: There is no tracked object with id '1'. Perhaps the DotNetObjectRef instance was already disposed.", result[2].GetString()); + Assert.Equal(callId, jsRuntime.LastCompletionCallId); + Assert.False(jsRuntime.LastCompletionStatus); // Fails + var result = Assert.IsType(jsRuntime.LastCompletionResult); + Assert.StartsWith("System.ArgumentException: There is no tracked object with id '1'. Perhaps the DotNetObjectRef instance was already disposed.", result.SourceException.ToString()); }); Task WithJSRuntime(Action testCode) @@ -556,6 +548,10 @@ namespace Microsoft.JSInterop.Tests public string LastInvocationIdentifier { get; private set; } public string LastInvocationArgsJson { get; private set; } + public string LastCompletionCallId { get; private set; } + public bool LastCompletionStatus { get; private set; } + public object LastCompletionResult { get; private set; } + protected override void BeginInvokeJS(long asyncHandle, string identifier, string argsJson) { LastInvocationAsyncHandle = asyncHandle; @@ -574,6 +570,21 @@ namespace Microsoft.JSInterop.Tests _nextInvocationTcs = new TaskCompletionSource(); return null; } + + protected internal override void EndInvokeDotNet( + string callId, + bool success, + object resultOrError, + string assemblyName, + string methodIdentifier, + long dotNetObjectId) + { + LastCompletionCallId = callId; + LastCompletionStatus = success; + LastCompletionResult = resultOrError; + _nextInvocationTcs.SetResult(null); + _nextInvocationTcs = new TaskCompletionSource(); + } } } } diff --git a/src/JSInterop/Microsoft.JSInterop/test/DotNetObjectRefTest.cs b/src/JSInterop/Microsoft.JSInterop/test/DotNetObjectRefTest.cs index 2b46831a16..77af280d94 100644 --- a/src/JSInterop/Microsoft.JSInterop/test/DotNetObjectRefTest.cs +++ b/src/JSInterop/Microsoft.JSInterop/test/DotNetObjectRefTest.cs @@ -37,6 +37,11 @@ namespace Microsoft.JSInterop.Tests { throw new NotImplementedException(); } + + protected internal override void EndInvokeDotNet(string callId, bool success, object resultOrError, string assemblyName, string methodIdentifier, long dotNetObjectId) + { + throw new NotImplementedException(); + } } async Task WithJSRuntime(Action testCode) diff --git a/src/JSInterop/Microsoft.JSInterop/test/JSInProcessRuntimeBaseTest.cs b/src/JSInterop/Microsoft.JSInterop/test/JSInProcessRuntimeBaseTest.cs index dda3f74184..a8d551e94e 100644 --- a/src/JSInterop/Microsoft.JSInterop/test/JSInProcessRuntimeBaseTest.cs +++ b/src/JSInterop/Microsoft.JSInterop/test/JSInProcessRuntimeBaseTest.cs @@ -115,6 +115,9 @@ namespace Microsoft.JSInterop.Tests protected override void BeginInvokeJS(long asyncHandle, string identifier, string argsJson) => throw new NotImplementedException("This test only covers sync calls"); + + protected internal override void EndInvokeDotNet(string callId, bool success, object resultOrError, string assemblyName, string methodIdentifier, long dotNetObjectId) => + throw new NotImplementedException("This test only covers sync calls"); } } } diff --git a/src/JSInterop/Microsoft.JSInterop/test/JSRuntimeBaseTest.cs b/src/JSInterop/Microsoft.JSInterop/test/JSRuntimeBaseTest.cs index 3ad78c303f..fb9227c996 100644 --- a/src/JSInterop/Microsoft.JSInterop/test/JSRuntimeBaseTest.cs +++ b/src/JSInterop/Microsoft.JSInterop/test/JSRuntimeBaseTest.cs @@ -4,10 +4,10 @@ using System; using System.Collections.Generic; using System.Linq; +using System.Runtime.ExceptionServices; using System.Text.Json; using System.Threading; using System.Threading.Tasks; -using Microsoft.JSInterop.Internal; using Xunit; namespace Microsoft.JSInterop.Tests @@ -79,7 +79,7 @@ namespace Microsoft.JSInterop.Tests var task = runtime.InvokeAsync("test identifier 1", new object[] { "arg1", 123, true }, cts.Token); cts.Cancel(); - + // Assert await Assert.ThrowsAsync(async () => await task); } @@ -248,13 +248,14 @@ namespace Microsoft.JSInterop.Tests var exception = new Exception("Some really sensitive data in here"); // Act - runtime.EndInvokeDotNet("0", false, exception, "Assembly", "Method"); + runtime.EndInvokeDotNet("0", false, exception, "Assembly", "Method", 0); // Assert - var call = runtime.BeginInvokeCalls.Single(); - Assert.Equal(0, call.AsyncHandle); - Assert.Equal("DotNet.jsCallDispatcher.endInvokeDotNetFromJS", call.Identifier); - Assert.Equal($"[\"0\",false,{{\"message\":\"{expectedMessage.Replace("'", "\\u0027")}\"}}]", call.ArgsJson); + var call = runtime.EndInvokeDotNetCalls.Single(); + Assert.Equal("0", call.CallId); + Assert.False(call.Success); + var jsError = Assert.IsType(call.ResultOrError); + Assert.Equal(expectedMessage, jsError.Message); } private class JSError @@ -265,6 +266,7 @@ namespace Microsoft.JSInterop.Tests class TestJSRuntime : JSRuntimeBase { public List BeginInvokeCalls = new List(); + public List EndInvokeDotNetCalls = new List(); public TimeSpan? DefaultTimeout { @@ -281,16 +283,28 @@ namespace Microsoft.JSInterop.Tests public string ArgsJson { get; set; } } + public class EndInvokeDotNetArgs + { + public string CallId { get; set; } + public bool Success { get; set; } + public object ResultOrError { get; set; } + } + public Func OnDotNetException { get; set; } - protected override object OnDotNetInvocationException(Exception exception, string assemblyName, string methodName) + protected internal override void EndInvokeDotNet(string callId, bool success, object resultOrError, string assemblyName, string methodIdentifier, long dotNetObjectId) { - if (OnDotNetException != null) + if (OnDotNetException != null && !success) { - return OnDotNetException(exception, assemblyName, methodName); + resultOrError = OnDotNetException(resultOrError as Exception, assemblyName, methodIdentifier); } - return base.OnDotNetInvocationException(exception, assemblyName, methodName); + EndInvokeDotNetCalls.Add(new EndInvokeDotNetArgs + { + CallId = callId, + Success = success, + ResultOrError = resultOrError + }); } protected override void BeginInvokeJS(long asyncHandle, string identifier, string argsJson) diff --git a/src/JSInterop/Mono.WebAssembly.Interop/ref/Mono.WebAssembly.Interop.netstandard2.0.cs b/src/JSInterop/Mono.WebAssembly.Interop/ref/Mono.WebAssembly.Interop.netstandard2.0.cs index 033e97237a..5299370576 100644 --- a/src/JSInterop/Mono.WebAssembly.Interop/ref/Mono.WebAssembly.Interop.netstandard2.0.cs +++ b/src/JSInterop/Mono.WebAssembly.Interop/ref/Mono.WebAssembly.Interop.netstandard2.0.cs @@ -7,6 +7,7 @@ namespace Mono.WebAssembly.Interop { public MonoWebAssemblyJSRuntime() { } protected override void BeginInvokeJS(long asyncHandle, string identifier, string argsJson) { } + protected override void EndInvokeDotNet(string callId, bool success, object resultOrError, string assemblyName, string methodIdentifier, long dotNetObjectId) { } protected override string InvokeJS(string identifier, string argsJson) { throw null; } public TRes InvokeUnmarshalled(string identifier) { throw null; } public TRes InvokeUnmarshalled(string identifier, T0 arg0) { throw null; } diff --git a/src/JSInterop/Mono.WebAssembly.Interop/src/MonoWebAssemblyJSRuntime.cs b/src/JSInterop/Mono.WebAssembly.Interop/src/MonoWebAssemblyJSRuntime.cs index 9a502b8bc8..e65df172f8 100644 --- a/src/JSInterop/Mono.WebAssembly.Interop/src/MonoWebAssemblyJSRuntime.cs +++ b/src/JSInterop/Mono.WebAssembly.Interop/src/MonoWebAssemblyJSRuntime.cs @@ -1,6 +1,9 @@ // 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.ExceptionServices; +using System.Text.Json; using Microsoft.JSInterop; using WebAssembly.JSInterop; @@ -32,6 +35,10 @@ namespace Mono.WebAssembly.Interop private static string InvokeDotNet(string assemblyName, string methodIdentifier, string dotNetObjectId, string argsJson) => DotNetDispatcher.Invoke(assemblyName, methodIdentifier, dotNetObjectId == null ? default : long.Parse(dotNetObjectId), argsJson); + // Invoked via Mono's JS interop mechanism (invoke_method) + private static void EndInvokeJS(string argsJson) + => DotNetDispatcher.EndInvoke(argsJson); + // Invoked via Mono's JS interop mechanism (invoke_method) private static void BeginInvokeDotNet(string callId, string assemblyNameOrDotNetObjectId, string methodIdentifier, string argsJson) { @@ -54,6 +61,32 @@ namespace Mono.WebAssembly.Interop DotNetDispatcher.BeginInvoke(callId, assemblyName, methodIdentifier, dotNetObjectId, argsJson); } + protected override void EndInvokeDotNet( + string callId, + bool success, + object resultOrError, + string assemblyName, + string methodIdentifier, + long dotNetObjectId) + { + // For failures, the common case is to call EndInvokeDotNet with the Exception object. + // For these we'll serialize as something that's useful to receive on the JS side. + // If the value is not an Exception, we'll just rely on it being directly JSON-serializable. + if (!success && resultOrError is Exception ex) + { + resultOrError = ex.ToString(); + } + else if (!success && resultOrError is ExceptionDispatchInfo edi) + { + resultOrError = edi.SourceException.ToString(); + } + + // We pass 0 as the async handle because we don't want the JS-side code to + // send back any notification (we're just providing a result for an existing async call) + var args = JsonSerializer.Serialize(new[] { callId, success, resultOrError }, new JsonSerializerOptions { PropertyNamingPolicy = JsonNamingPolicy.CamelCase }); + BeginInvokeJS(0, "DotNet.jsCallDispatcher.endInvokeDotNetFromJS", args); + } + #region Custom MonoWebAssemblyJSRuntime methods /// From 2d2400e7faf5c354c785b606e8a59afadbbb9c19 Mon Sep 17 00:00:00 2001 From: Pranav K Date: Tue, 16 Jul 2019 15:21:28 -0700 Subject: [PATCH 0126/1101] Use JsonConverter for DotNetObjectRef \n\nCommit migrated from https://github.com/dotnet/extensions/commit/0389bf44d4833843801061c744599b26e32268d8 --- .../ref/Microsoft.JSInterop.netstandard2.0.cs | 6 +- .../src/DotNetDispatcher.cs | 4 +- .../src/DotNetObjectRef.cs | 3 +- .../DotNetObjectRefJsonConverterFactory.cs | 29 +++++++ .../src/DotNetObjectRefManager.cs | 6 +- .../src/DotNetObjectRefOfT.cs | 43 ++-------- .../src/DotNetObjectReferenceJsonConverter.cs | 51 ++++++++++++ .../src/IDotNetObjectRef.cs | 1 - .../Microsoft.JSInterop/test/Class1.cs | 27 +++++++ .../test/DotNetDispatcherTest.cs | 2 +- .../test/DotNetObjectRefTest.cs | 12 +-- .../DotNetObjectReferenceJsonConverterTest.cs | 78 +++++++++++++++++++ 12 files changed, 204 insertions(+), 58 deletions(-) create mode 100644 src/JSInterop/Microsoft.JSInterop/src/DotNetObjectRefJsonConverterFactory.cs create mode 100644 src/JSInterop/Microsoft.JSInterop/src/DotNetObjectReferenceJsonConverter.cs create mode 100644 src/JSInterop/Microsoft.JSInterop/test/Class1.cs create mode 100644 src/JSInterop/Microsoft.JSInterop/test/DotNetObjectReferenceJsonConverterTest.cs diff --git a/src/JSInterop/Microsoft.JSInterop/ref/Microsoft.JSInterop.netstandard2.0.cs b/src/JSInterop/Microsoft.JSInterop/ref/Microsoft.JSInterop.netstandard2.0.cs index 125a555c9d..654ae9d617 100644 --- a/src/JSInterop/Microsoft.JSInterop/ref/Microsoft.JSInterop.netstandard2.0.cs +++ b/src/JSInterop/Microsoft.JSInterop/ref/Microsoft.JSInterop.netstandard2.0.cs @@ -17,12 +17,8 @@ namespace Microsoft.JSInterop } public sealed partial class DotNetObjectRef : System.IDisposable where TValue : class { - [System.ComponentModel.EditorBrowsableAttribute(System.ComponentModel.EditorBrowsableState.Never)] - public DotNetObjectRef() { } - [System.Text.Json.Serialization.JsonIgnoreAttribute] + internal DotNetObjectRef() { } public TValue Value { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } } - [System.ComponentModel.EditorBrowsableAttribute(System.ComponentModel.EditorBrowsableState.Never)] - public long __dotNetObject { get { throw null; } set { } } public void Dispose() { } } public partial interface IJSInProcessRuntime : Microsoft.JSInterop.IJSRuntime diff --git a/src/JSInterop/Microsoft.JSInterop/src/DotNetDispatcher.cs b/src/JSInterop/Microsoft.JSInterop/src/DotNetDispatcher.cs index 60b6ad9ab0..3de8f55882 100644 --- a/src/JSInterop/Microsoft.JSInterop/src/DotNetDispatcher.cs +++ b/src/JSInterop/Microsoft.JSInterop/src/DotNetDispatcher.cs @@ -17,7 +17,7 @@ namespace Microsoft.JSInterop /// public static class DotNetDispatcher { - internal const string DotNetObjectRefKey = nameof(DotNetObjectRef.__dotNetObject); + internal static readonly JsonEncodedText DotNetObjectRefKey = JsonEncodedText.Encode("__dotNetObject"); private static readonly Type[] EndInvokeParameterTypes = new Type[] { typeof(long), typeof(bool), typeof(JSAsyncCallResult) }; private static readonly ConcurrentDictionary> _cachedMethodsByAssembly @@ -238,7 +238,7 @@ namespace Microsoft.JSInterop // an incorrect use if there's a object that looks like { '__dotNetObject': }, // but we aren't assigning to DotNetObjectRef{T}. return item.ValueKind == JsonValueKind.Object && - item.TryGetProperty(DotNetObjectRefKey, out _) && + item.TryGetProperty(DotNetObjectRefKey.EncodedUtf8Bytes, out _) && !typeof(IDotNetObjectRef).IsAssignableFrom(parameterType); } } diff --git a/src/JSInterop/Microsoft.JSInterop/src/DotNetObjectRef.cs b/src/JSInterop/Microsoft.JSInterop/src/DotNetObjectRef.cs index 1aabc5ad59..af790281e9 100644 --- a/src/JSInterop/Microsoft.JSInterop/src/DotNetObjectRef.cs +++ b/src/JSInterop/Microsoft.JSInterop/src/DotNetObjectRef.cs @@ -15,7 +15,8 @@ namespace Microsoft.JSInterop /// An instance of . public static DotNetObjectRef Create(TValue value) where TValue : class { - return new DotNetObjectRef(value); + var objectId = DotNetObjectRefManager.Current.TrackObject(value); + return new DotNetObjectRef(objectId, value); } } } diff --git a/src/JSInterop/Microsoft.JSInterop/src/DotNetObjectRefJsonConverterFactory.cs b/src/JSInterop/Microsoft.JSInterop/src/DotNetObjectRefJsonConverterFactory.cs new file mode 100644 index 0000000000..3bc1f83c7e --- /dev/null +++ b/src/JSInterop/Microsoft.JSInterop/src/DotNetObjectRefJsonConverterFactory.cs @@ -0,0 +1,29 @@ +// 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.Diagnostics; +using System.Text.Json.Serialization; + +namespace Microsoft.JSInterop +{ + internal sealed class DotNetObjectReferenceJsonConverterFactory : JsonConverterFactory + { + public override bool CanConvert(Type typeToConvert) + { + Debug.Assert( + typeToConvert.IsGenericType && typeToConvert.GetGenericTypeDefinition() == typeof(DotNetObjectRef<>), + "We expect this to only be called for DotNetObjectRef instances."); + return true; + } + + protected override JsonConverter CreateConverter(Type typeToConvert) + { + // System.Text.Json handles caching the converters per type on our behalf. No caching is required here. + var instanceType = typeToConvert.GetGenericArguments()[0]; + var converterType = typeof(DotNetObjectReferenceJsonConverter<>).MakeGenericType(instanceType); + + return (JsonConverter)Activator.CreateInstance(converterType); + } + } +} diff --git a/src/JSInterop/Microsoft.JSInterop/src/DotNetObjectRefManager.cs b/src/JSInterop/Microsoft.JSInterop/src/DotNetObjectRefManager.cs index f263716f53..ad1469e38f 100644 --- a/src/JSInterop/Microsoft.JSInterop/src/DotNetObjectRefManager.cs +++ b/src/JSInterop/Microsoft.JSInterop/src/DotNetObjectRefManager.cs @@ -10,7 +10,7 @@ namespace Microsoft.JSInterop internal class DotNetObjectRefManager { private long _nextId = 0; // 0 signals no object, but we increment prior to assignment. The first tracked object should have id 1 - private readonly ConcurrentDictionary _trackedRefsById = new ConcurrentDictionary(); + private readonly ConcurrentDictionary _trackedRefsById = new ConcurrentDictionary(); public static DotNetObjectRefManager Current { @@ -25,7 +25,7 @@ namespace Microsoft.JSInterop } } - public long TrackObject(IDotNetObjectRef dotNetObjectRef) + public long TrackObject(object dotNetObjectRef) { var dotNetObjectId = Interlocked.Increment(ref _nextId); _trackedRefsById[dotNetObjectId] = dotNetObjectRef; @@ -36,7 +36,7 @@ namespace Microsoft.JSInterop public object FindDotNetObject(long dotNetObjectId) { return _trackedRefsById.TryGetValue(dotNetObjectId, out var dotNetObjectRef) - ? dotNetObjectRef.Value + ? dotNetObjectRef : throw new ArgumentException($"There is no tracked object with id '{dotNetObjectId}'. Perhaps the DotNetObjectRef instance was already disposed.", nameof(dotNetObjectId)); } diff --git a/src/JSInterop/Microsoft.JSInterop/src/DotNetObjectRefOfT.cs b/src/JSInterop/Microsoft.JSInterop/src/DotNetObjectRefOfT.cs index 8b7035e957..be6bf91663 100644 --- a/src/JSInterop/Microsoft.JSInterop/src/DotNetObjectRefOfT.cs +++ b/src/JSInterop/Microsoft.JSInterop/src/DotNetObjectRefOfT.cs @@ -2,7 +2,6 @@ // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; -using System.ComponentModel; using System.Text.Json.Serialization; namespace Microsoft.JSInterop @@ -14,54 +13,26 @@ namespace Microsoft.JSInterop /// To avoid leaking memory, the reference must later be disposed by JS code or by .NET code. /// /// The type of the value to wrap. + [JsonConverter(typeof(DotNetObjectReferenceJsonConverterFactory))] public sealed class DotNetObjectRef : IDotNetObjectRef, IDisposable where TValue : class { - private long? _trackingId; - - /// - /// This API is for meant for JSON deserialization and should not be used by user code. - /// - [EditorBrowsable(EditorBrowsableState.Never)] - public DotNetObjectRef() - { - } - /// /// Initializes a new instance of . /// + /// The object Id. /// The value to pass by reference. - internal DotNetObjectRef(TValue value) + internal DotNetObjectRef(long objectId, TValue value) { + ObjectId = objectId; Value = value; - _trackingId = DotNetObjectRefManager.Current.TrackObject(this); } /// /// Gets the object instance represented by this wrapper. /// - [JsonIgnore] - public TValue Value { get; private set; } + public TValue Value { get; } - /// - /// This API is for meant for JSON serialization and should not be used by user code. - /// - [EditorBrowsable(EditorBrowsableState.Never)] - public long __dotNetObject - { - get => _trackingId.Value; - set - { - if (_trackingId != null) - { - throw new InvalidOperationException($"{nameof(DotNetObjectRef)} cannot be reinitialized."); - } - - _trackingId = value; - Value = (TValue)DotNetObjectRefManager.Current.FindDotNetObject(value); - } - } - - object IDotNetObjectRef.Value => Value; + internal long ObjectId { get; } /// /// Stops tracking this object reference, allowing it to be garbage collected @@ -70,7 +41,7 @@ namespace Microsoft.JSInterop /// public void Dispose() { - DotNetObjectRefManager.Current.ReleaseDotNetObject(_trackingId.Value); + DotNetObjectRefManager.Current.ReleaseDotNetObject(ObjectId); } } } diff --git a/src/JSInterop/Microsoft.JSInterop/src/DotNetObjectReferenceJsonConverter.cs b/src/JSInterop/Microsoft.JSInterop/src/DotNetObjectReferenceJsonConverter.cs new file mode 100644 index 0000000000..d07735734e --- /dev/null +++ b/src/JSInterop/Microsoft.JSInterop/src/DotNetObjectReferenceJsonConverter.cs @@ -0,0 +1,51 @@ +// 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.IO; +using System.Text.Json; +using System.Text.Json.Serialization; + +namespace Microsoft.JSInterop +{ + internal sealed class DotNetObjectReferenceJsonConverter : JsonConverter> where TValue : class + { + private static JsonEncodedText DotNetObjectRefKey => DotNetDispatcher.DotNetObjectRefKey; + + public override DotNetObjectRef Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + { + if (!reader.Read()) + { + throw new InvalidDataException("Invalid DotNetObjectRef JSON."); + } + + if (reader.TokenType != JsonTokenType.PropertyName || !reader.ValueTextEquals(DotNetObjectRefKey.EncodedUtf8Bytes)) + { + throw new InvalidDataException("Invalid DotNetObjectRef JSON."); + } + + if (!reader.Read()) + { + throw new InvalidDataException("Invalid DotNetObjectRef JSON."); + } + + var dotNetObjectId = reader.GetInt64(); + + if (!reader.Read()) + { + // We need to read all the data that was given to us. + throw new InvalidDataException("Invalid DotNetObjectRef JSON."); + } + + var value = (TValue)DotNetObjectRefManager.Current.FindDotNetObject(dotNetObjectId); + return new DotNetObjectRef(dotNetObjectId, value); + } + + public override void Write(Utf8JsonWriter writer, DotNetObjectRef value, JsonSerializerOptions options) + { + writer.WriteStartObject(); + writer.WriteNumber(DotNetObjectRefKey, value.ObjectId); + writer.WriteEndObject(); + } + } +} diff --git a/src/JSInterop/Microsoft.JSInterop/src/IDotNetObjectRef.cs b/src/JSInterop/Microsoft.JSInterop/src/IDotNetObjectRef.cs index 5f21808a9f..b082d0ce10 100644 --- a/src/JSInterop/Microsoft.JSInterop/src/IDotNetObjectRef.cs +++ b/src/JSInterop/Microsoft.JSInterop/src/IDotNetObjectRef.cs @@ -7,6 +7,5 @@ namespace Microsoft.JSInterop { internal interface IDotNetObjectRef : IDisposable { - public object Value { get; } } } diff --git a/src/JSInterop/Microsoft.JSInterop/test/Class1.cs b/src/JSInterop/Microsoft.JSInterop/test/Class1.cs new file mode 100644 index 0000000000..1e58139b36 --- /dev/null +++ b/src/JSInterop/Microsoft.JSInterop/test/Class1.cs @@ -0,0 +1,27 @@ +// 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.Threading.Tasks; + +namespace Microsoft.JSInterop +{ + internal class TestJSRuntime : JSRuntimeBase + { + protected override void BeginInvokeJS(long asyncHandle, string identifier, string argsJson) + { + throw new NotImplementedException(); + } + + public static async Task WithJSRuntime(Action testCode) + { + // Since the tests rely on the asynclocal JSRuntime.Current, ensure we + // are on a distinct async context with a non-null JSRuntime.Current + await Task.Yield(); + + var runtime = new TestJSRuntime(); + JSRuntime.SetCurrentJSRuntime(runtime); + testCode(runtime); + } + } +} diff --git a/src/JSInterop/Microsoft.JSInterop/test/DotNetDispatcherTest.cs b/src/JSInterop/Microsoft.JSInterop/test/DotNetDispatcherTest.cs index a4a0de5a3f..1c73cc63b2 100644 --- a/src/JSInterop/Microsoft.JSInterop/test/DotNetDispatcherTest.cs +++ b/src/JSInterop/Microsoft.JSInterop/test/DotNetDispatcherTest.cs @@ -142,7 +142,7 @@ namespace Microsoft.JSInterop.Tests Assert.False(resultDto2Ref.TryGetProperty(nameof(TestDTO.StringVal), out _)); Assert.False(resultDto2Ref.TryGetProperty(nameof(TestDTO.IntVal), out _)); - Assert.True(resultDto2Ref.TryGetProperty(DotNetDispatcher.DotNetObjectRefKey, out var property)); + Assert.True(resultDto2Ref.TryGetProperty(DotNetDispatcher.DotNetObjectRefKey.EncodedUtf8Bytes, out var property)); var resultDto2 = Assert.IsType(DotNetObjectRefManager.Current.FindDotNetObject(property.GetInt64())); Assert.Equal("MY STRING", resultDto2.StringVal); Assert.Equal(1299, resultDto2.IntVal); diff --git a/src/JSInterop/Microsoft.JSInterop/test/DotNetObjectRefTest.cs b/src/JSInterop/Microsoft.JSInterop/test/DotNetObjectRefTest.cs index 77af280d94..f417985746 100644 --- a/src/JSInterop/Microsoft.JSInterop/test/DotNetObjectRefTest.cs +++ b/src/JSInterop/Microsoft.JSInterop/test/DotNetObjectRefTest.cs @@ -4,8 +4,9 @@ using System; using System.Threading.Tasks; using Xunit; +using static Microsoft.JSInterop.TestJSRuntime; -namespace Microsoft.JSInterop.Tests +namespace Microsoft.JSInterop { public class DotNetObjectRefTest { @@ -21,21 +22,14 @@ namespace Microsoft.JSInterop.Tests { // Arrange var objRef = DotNetObjectRef.Create(new object()); - var trackingId = objRef.__dotNetObject; // Act objRef.Dispose(); // Assert - var ex = Assert.Throws(() => jsRuntime.ObjectRefManager.FindDotNetObject(trackingId)); + var ex = Assert.Throws(() => jsRuntime.ObjectRefManager.FindDotNetObject(objRef.ObjectId)); Assert.StartsWith("There is no tracked object with id '1'.", ex.Message); }); - - class TestJSRuntime : JSRuntimeBase - { - protected override void BeginInvokeJS(long asyncHandle, string identifier, string argsJson) - { - throw new NotImplementedException(); } protected internal override void EndInvokeDotNet(string callId, bool success, object resultOrError, string assemblyName, string methodIdentifier, long dotNetObjectId) diff --git a/src/JSInterop/Microsoft.JSInterop/test/DotNetObjectReferenceJsonConverterTest.cs b/src/JSInterop/Microsoft.JSInterop/test/DotNetObjectReferenceJsonConverterTest.cs new file mode 100644 index 0000000000..c2dbd88895 --- /dev/null +++ b/src/JSInterop/Microsoft.JSInterop/test/DotNetObjectReferenceJsonConverterTest.cs @@ -0,0 +1,78 @@ +// 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.Text.Json; +using System.Threading.Tasks; +using Xunit; +using static Microsoft.JSInterop.TestJSRuntime; + +namespace Microsoft.JSInterop.Tests +{ + public class DotNetObjectReferenceJsonConverterTest + { + [Fact] + public Task Read_ReadsJson() => WithJSRuntime(_ => + { + // Arrange + // Throw-away value + DotNetObjectRef.Create(new TestModel()); + var input = new TestModel(); + var dotNetObjectRef = DotNetObjectRef.Create(input); + var objectId = dotNetObjectRef.ObjectId; + + var json = $"{{\"__dotNetObject\":{objectId}}}"; + + // Act + var deserialized = JsonSerializer.Deserialize>(json); + + // Assert + Assert.Same(input, deserialized.Value); + Assert.Equal(objectId, deserialized.ObjectId); + }); + + [Fact] + public Task Read_ReadsJson_WithFormatting() => WithJSRuntime(_ => + { + // Arrange + // Throw-away value + DotNetObjectRef.Create(new TestModel()); + var input = new TestModel(); + var dotNetObjectRef = DotNetObjectRef.Create(input); + var objectId = dotNetObjectRef.ObjectId; + + var json = +@$"{{ + ""__dotNetObject"": {objectId} +}}"; + + // Act + var deserialized = JsonSerializer.Deserialize>(json); + + // Assert + Assert.Same(input, deserialized.Value); + Assert.Equal(objectId, deserialized.ObjectId); + }); + + [Fact] + public Task WriteJsonTwice_KeepsObjectId() => WithJSRuntime(_ => + { + // Arrange + // Throw-away value + DotNetObjectRef.Create(new TestModel()); + var dotNetObjectRef = DotNetObjectRef.Create(new TestModel()); + + // Act + var json1 = JsonSerializer.Serialize(dotNetObjectRef); + var json2 = JsonSerializer.Serialize(dotNetObjectRef); + + // Assert + Assert.Equal($"{{\"__dotNetObject\":{dotNetObjectRef.ObjectId}}}", json1); + Assert.Equal(json1, json2); + }); + + private class TestModel + { + + } + } +} From 950b3873b138a7f27bc2d41200319785bda093df Mon Sep 17 00:00:00 2001 From: Pranav K Date: Tue, 16 Jul 2019 16:15:48 -0700 Subject: [PATCH 0127/1101] PR comments \n\nCommit migrated from https://github.com/dotnet/extensions/commit/791c96b143d3f4e1e0b507cd0283cdf09ac0adb6 --- .../DotNetObjectRefJsonConverterFactory.cs | 9 ++-- .../src/DotNetObjectReferenceJsonConverter.cs | 39 +++++++------- .../test/DotNetObjectRefTest.cs | 18 ------- .../DotNetObjectReferenceJsonConverterTest.cs | 52 +++++++++++++++++-- .../test/{Class1.cs => TestJSRuntime.cs} | 5 ++ 5 files changed, 76 insertions(+), 47 deletions(-) rename src/JSInterop/Microsoft.JSInterop/test/{Class1.cs => TestJSRuntime.cs} (78%) diff --git a/src/JSInterop/Microsoft.JSInterop/src/DotNetObjectRefJsonConverterFactory.cs b/src/JSInterop/Microsoft.JSInterop/src/DotNetObjectRefJsonConverterFactory.cs index 3bc1f83c7e..5cfec5be9d 100644 --- a/src/JSInterop/Microsoft.JSInterop/src/DotNetObjectRefJsonConverterFactory.cs +++ b/src/JSInterop/Microsoft.JSInterop/src/DotNetObjectRefJsonConverterFactory.cs @@ -2,7 +2,7 @@ // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; -using System.Diagnostics; +using System.Text.Json; using System.Text.Json.Serialization; namespace Microsoft.JSInterop @@ -11,13 +11,10 @@ namespace Microsoft.JSInterop { public override bool CanConvert(Type typeToConvert) { - Debug.Assert( - typeToConvert.IsGenericType && typeToConvert.GetGenericTypeDefinition() == typeof(DotNetObjectRef<>), - "We expect this to only be called for DotNetObjectRef instances."); - return true; + return typeToConvert.IsGenericType && typeToConvert.GetGenericTypeDefinition() == typeof(DotNetObjectRef<>); } - protected override JsonConverter CreateConverter(Type typeToConvert) + public override JsonConverter CreateConverter(Type typeToConvert, JsonSerializerOptions jsonSerializerOptions) { // System.Text.Json handles caching the converters per type on our behalf. No caching is required here. var instanceType = typeToConvert.GetGenericArguments()[0]; diff --git a/src/JSInterop/Microsoft.JSInterop/src/DotNetObjectReferenceJsonConverter.cs b/src/JSInterop/Microsoft.JSInterop/src/DotNetObjectReferenceJsonConverter.cs index d07735734e..487b4c77b0 100644 --- a/src/JSInterop/Microsoft.JSInterop/src/DotNetObjectReferenceJsonConverter.cs +++ b/src/JSInterop/Microsoft.JSInterop/src/DotNetObjectReferenceJsonConverter.cs @@ -2,7 +2,6 @@ // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; -using System.IO; using System.Text.Json; using System.Text.Json.Serialization; @@ -14,27 +13,31 @@ namespace Microsoft.JSInterop public override DotNetObjectRef Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) { - if (!reader.Read()) + long dotNetObjectId = 0; + + while (reader.Read() && reader.TokenType != JsonTokenType.EndObject) { - throw new InvalidDataException("Invalid DotNetObjectRef JSON."); + if (reader.TokenType == JsonTokenType.PropertyName) + { + if (reader.ValueTextEquals(DotNetObjectRefKey.EncodedUtf8Bytes)) + { + reader.Read(); + dotNetObjectId = reader.GetInt64(); + } + else + { + throw new JsonException($"Unexcepted JSON property {reader.GetString()}."); + } + } + else + { + throw new JsonException($"Unexcepted JSON Token {reader.TokenType}."); + } } - if (reader.TokenType != JsonTokenType.PropertyName || !reader.ValueTextEquals(DotNetObjectRefKey.EncodedUtf8Bytes)) + if (dotNetObjectId is 0) { - throw new InvalidDataException("Invalid DotNetObjectRef JSON."); - } - - if (!reader.Read()) - { - throw new InvalidDataException("Invalid DotNetObjectRef JSON."); - } - - var dotNetObjectId = reader.GetInt64(); - - if (!reader.Read()) - { - // We need to read all the data that was given to us. - throw new InvalidDataException("Invalid DotNetObjectRef JSON."); + throw new JsonException($"Required property {DotNetObjectRefKey} not found."); } var value = (TValue)DotNetObjectRefManager.Current.FindDotNetObject(dotNetObjectId); diff --git a/src/JSInterop/Microsoft.JSInterop/test/DotNetObjectRefTest.cs b/src/JSInterop/Microsoft.JSInterop/test/DotNetObjectRefTest.cs index f417985746..112363869e 100644 --- a/src/JSInterop/Microsoft.JSInterop/test/DotNetObjectRefTest.cs +++ b/src/JSInterop/Microsoft.JSInterop/test/DotNetObjectRefTest.cs @@ -30,23 +30,5 @@ namespace Microsoft.JSInterop var ex = Assert.Throws(() => jsRuntime.ObjectRefManager.FindDotNetObject(objRef.ObjectId)); Assert.StartsWith("There is no tracked object with id '1'.", ex.Message); }); - } - - protected internal override void EndInvokeDotNet(string callId, bool success, object resultOrError, string assemblyName, string methodIdentifier, long dotNetObjectId) - { - throw new NotImplementedException(); - } - } - - async Task WithJSRuntime(Action testCode) - { - // Since the tests rely on the asynclocal JSRuntime.Current, ensure we - // are on a distinct async context with a non-null JSRuntime.Current - await Task.Yield(); - - var runtime = new TestJSRuntime(); - JSRuntime.SetCurrentJSRuntime(runtime); - testCode(runtime); - } } } diff --git a/src/JSInterop/Microsoft.JSInterop/test/DotNetObjectReferenceJsonConverterTest.cs b/src/JSInterop/Microsoft.JSInterop/test/DotNetObjectReferenceJsonConverterTest.cs index c2dbd88895..be1d2bf975 100644 --- a/src/JSInterop/Microsoft.JSInterop/test/DotNetObjectReferenceJsonConverterTest.cs +++ b/src/JSInterop/Microsoft.JSInterop/test/DotNetObjectReferenceJsonConverterTest.cs @@ -10,12 +10,58 @@ namespace Microsoft.JSInterop.Tests { public class DotNetObjectReferenceJsonConverterTest { + [Fact] + public Task Read_Throws_IfJsonIsMissingDotNetObjectProperty() => WithJSRuntime(_ => + { + // Arrange + var dotNetObjectRef = DotNetObjectRef.Create(new TestModel()); + + var json = "{}"; + + // Act & Assert + var ex = Assert.Throws(() => JsonSerializer.Deserialize>(json)); + Assert.Equal("Required property __dotNetObject not found.", ex.Message); + }); + + [Fact] + public Task Read_Throws_IfJsonContainsUnknownContent() => WithJSRuntime(_ => + { + // Arrange + var dotNetObjectRef = DotNetObjectRef.Create(new TestModel()); + + var json = "{\"foo\":2}"; + + // Act & Assert + var ex = Assert.Throws(() => JsonSerializer.Deserialize>(json)); + Assert.Equal("Unexcepted JSON property foo.", ex.Message); + }); + [Fact] public Task Read_ReadsJson() => WithJSRuntime(_ => { // Arrange - // Throw-away value + var input = new TestModel(); + var dotNetObjectRef = DotNetObjectRef.Create(input); + var objectId = dotNetObjectRef.ObjectId; + + var json = $"{{\"__dotNetObject\":{objectId}}}"; + + // Act + var deserialized = JsonSerializer.Deserialize>(json); + + // Assert + Assert.Same(input, deserialized.Value); + Assert.Equal(objectId, deserialized.ObjectId); + }); + + [Fact] + public Task Read_ReturnsTheCorrectInstance() => WithJSRuntime(_ => + { + // Arrange + // Track a few instances and verify that the deserialized value returns the corect value. DotNetObjectRef.Create(new TestModel()); + DotNetObjectRef.Create(new TestModel()); + var input = new TestModel(); var dotNetObjectRef = DotNetObjectRef.Create(input); var objectId = dotNetObjectRef.ObjectId; @@ -34,8 +80,6 @@ namespace Microsoft.JSInterop.Tests public Task Read_ReadsJson_WithFormatting() => WithJSRuntime(_ => { // Arrange - // Throw-away value - DotNetObjectRef.Create(new TestModel()); var input = new TestModel(); var dotNetObjectRef = DotNetObjectRef.Create(input); var objectId = dotNetObjectRef.ObjectId; @@ -57,8 +101,6 @@ namespace Microsoft.JSInterop.Tests public Task WriteJsonTwice_KeepsObjectId() => WithJSRuntime(_ => { // Arrange - // Throw-away value - DotNetObjectRef.Create(new TestModel()); var dotNetObjectRef = DotNetObjectRef.Create(new TestModel()); // Act diff --git a/src/JSInterop/Microsoft.JSInterop/test/Class1.cs b/src/JSInterop/Microsoft.JSInterop/test/TestJSRuntime.cs similarity index 78% rename from src/JSInterop/Microsoft.JSInterop/test/Class1.cs rename to src/JSInterop/Microsoft.JSInterop/test/TestJSRuntime.cs index 1e58139b36..c4e6b05c5b 100644 --- a/src/JSInterop/Microsoft.JSInterop/test/Class1.cs +++ b/src/JSInterop/Microsoft.JSInterop/test/TestJSRuntime.cs @@ -13,6 +13,11 @@ namespace Microsoft.JSInterop throw new NotImplementedException(); } + protected internal override void EndInvokeDotNet(string callId, bool success, object resultOrError, string assemblyName, string methodIdentifier, long dotNetObjectId) + { + throw new NotImplementedException(); + } + public static async Task WithJSRuntime(Action testCode) { // Since the tests rely on the asynclocal JSRuntime.Current, ensure we From 39c052b8bce2628a430697f4fb16d4207872ef80 Mon Sep 17 00:00:00 2001 From: Pranav K Date: Tue, 23 Jul 2019 19:36:16 -0700 Subject: [PATCH 0128/1101] More \n\nCommit migrated from https://github.com/dotnet/extensions/commit/0cf3c01afc54980d5d49d31b233b01eafd60e104 --- .../src/DotNetObjectReferenceJsonConverter.cs | 2 +- .../DotNetObjectReferenceJsonConverterTest.cs | 51 +++++++++++++++---- 2 files changed, 41 insertions(+), 12 deletions(-) diff --git a/src/JSInterop/Microsoft.JSInterop/src/DotNetObjectReferenceJsonConverter.cs b/src/JSInterop/Microsoft.JSInterop/src/DotNetObjectReferenceJsonConverter.cs index 487b4c77b0..eaabdbf9e6 100644 --- a/src/JSInterop/Microsoft.JSInterop/src/DotNetObjectReferenceJsonConverter.cs +++ b/src/JSInterop/Microsoft.JSInterop/src/DotNetObjectReferenceJsonConverter.cs @@ -19,7 +19,7 @@ namespace Microsoft.JSInterop { if (reader.TokenType == JsonTokenType.PropertyName) { - if (reader.ValueTextEquals(DotNetObjectRefKey.EncodedUtf8Bytes)) + if (dotNetObjectId == 0 && reader.ValueTextEquals(DotNetObjectRefKey.EncodedUtf8Bytes)) { reader.Read(); dotNetObjectId = reader.GetInt64(); diff --git a/src/JSInterop/Microsoft.JSInterop/test/DotNetObjectReferenceJsonConverterTest.cs b/src/JSInterop/Microsoft.JSInterop/test/DotNetObjectReferenceJsonConverterTest.cs index be1d2bf975..18f3db55c1 100644 --- a/src/JSInterop/Microsoft.JSInterop/test/DotNetObjectReferenceJsonConverterTest.cs +++ b/src/JSInterop/Microsoft.JSInterop/test/DotNetObjectReferenceJsonConverterTest.cs @@ -36,6 +36,36 @@ namespace Microsoft.JSInterop.Tests Assert.Equal("Unexcepted JSON property foo.", ex.Message); }); + [Fact] + public Task Read_Throws_IfJsonIsIncomplete() => WithJSRuntime(_ => + { + // Arrange + var input = new TestModel(); + var dotNetObjectRef = DotNetObjectRef.Create(input); + var objectId = dotNetObjectRef.ObjectId; + + var json = $"{{\"__dotNetObject\":{objectId}"; + + // Act & Assert + var ex = Record.Exception(() => JsonSerializer.Deserialize>(json)); + Assert.IsAssignableFrom(ex); + }); + + [Fact] + public Task Read_Throws_IfDotNetObjectIdAppearsMultipleTimes() => WithJSRuntime(_ => + { + // Arrange + var input = new TestModel(); + var dotNetObjectRef = DotNetObjectRef.Create(input); + var objectId = dotNetObjectRef.ObjectId; + + var json = $"{{\"__dotNetObject\":{objectId},\"__dotNetObject\":{objectId}}}"; + + // Act & Assert + var ex = Record.Exception(() => JsonSerializer.Deserialize>(json)); + Assert.IsAssignableFrom(ex); + }); + [Fact] public Task Read_ReadsJson() => WithJSRuntime(_ => { @@ -54,26 +84,25 @@ namespace Microsoft.JSInterop.Tests Assert.Equal(objectId, deserialized.ObjectId); }); + [Fact] public Task Read_ReturnsTheCorrectInstance() => WithJSRuntime(_ => { // Arrange - // Track a few instances and verify that the deserialized value returns the corect value. - DotNetObjectRef.Create(new TestModel()); - DotNetObjectRef.Create(new TestModel()); + // Track a few instances and verify that the deserialized value returns the correct value. + var instance1 = new TestModel(); + var instance2 = new TestModel(); + var ref1 = DotNetObjectRef.Create(instance1); + var ref2 = DotNetObjectRef.Create(instance2); - var input = new TestModel(); - var dotNetObjectRef = DotNetObjectRef.Create(input); - var objectId = dotNetObjectRef.ObjectId; - - var json = $"{{\"__dotNetObject\":{objectId}}}"; + var json = $"[{{\"__dotNetObject\":{ref2.ObjectId}}},{{\"__dotNetObject\":{ref1.ObjectId}}}]"; // Act - var deserialized = JsonSerializer.Deserialize>(json); + var deserialized = JsonSerializer.Deserialize[]>(json); // Assert - Assert.Same(input, deserialized.Value); - Assert.Equal(objectId, deserialized.ObjectId); + Assert.Same(instance2, deserialized[0].Value); + Assert.Same(instance1, deserialized[1].Value); }); [Fact] From ef99d81d681c6d3ddf8af9c7c49dde7f4afb5170 Mon Sep 17 00:00:00 2001 From: John Luo Date: Mon, 29 Jul 2019 20:32:24 -0700 Subject: [PATCH 0129/1101] Update branding to 5.0.0-alpha1 (#12671) * Update branding to 5.0.0-alpha1 * Preserve baseline reference overrides for 5.0 * Skip framework tests --- Directory.Build.props | 2 +- eng/Versions.props | 13 +++++++++---- ...ft.AspNetCore.DataProtection.AzureStorage.csproj | 2 +- .../test/Microsoft.AspNetCore.App.UnitTests.csproj | 2 ++ .../scripts/Run-Angular-Locally.ps1 | 2 +- src/ProjectTemplates/scripts/Run-Blazor-Locally.ps1 | 2 +- .../scripts/Run-EmptyWeb-Locally.ps1 | 2 +- src/ProjectTemplates/scripts/Run-Razor-Locally.ps1 | 2 +- src/ProjectTemplates/scripts/Run-React-Locally.ps1 | 2 +- .../scripts/Run-ReactRedux-Locally.ps1 | 2 +- .../scripts/Run-Starterweb-Locally.ps1 | 2 +- src/ProjectTemplates/scripts/Run-Worker-Locally.ps1 | 2 +- .../test/Helpers/TemplatePackageInstaller.cs | 2 ++ .../Microsoft.AspNetCore.SignalR.Client.Core.csproj | 2 +- .../ts/signalr-protocol-msgpack/package.json | 2 +- src/SignalR/clients/ts/signalr/package.json | 2 +- ...rosoft.AspNetCore.Http.Connections.Common.csproj | 2 +- ...crosoft.AspNetCore.SignalR.Protocols.Json.csproj | 2 +- .../src/Microsoft.AspNetCore.SignalR.Common.csproj | 2 +- ...AspNetCore.AzureAppServices.SiteExtension.csproj | 4 ++-- 20 files changed, 31 insertions(+), 22 deletions(-) diff --git a/Directory.Build.props b/Directory.Build.props index 06e288aa81..4b87f1845a 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -43,7 +43,7 @@ true - netcoreapp$(AspNetCoreMajorVersion).$(AspNetCoreMinorVersion) + netcoreapp$(TFMNetCoreMajorVersion).$(TFMNetCoreMinorVersion) diff --git a/eng/Versions.props b/eng/Versions.props index 8f27f6529a..1f758e7105 100644 --- a/eng/Versions.props +++ b/eng/Versions.props @@ -6,12 +6,12 @@ --> - 3 + 5 0 0 - 8 - preview$(PreReleasePreviewNumber) - Preview $(PreReleasePreviewNumber) + 1 + alpha$(PreReleasePreviewNumber) + Alpha $(PreReleasePreviewNumber) $(AspNetCoreMajorVersion).$(AspNetCoreMinorVersion) false @@ -38,6 +38,11 @@ false + + + 3 + 0 + diff --git a/src/Framework/test/Microsoft.AspNetCore.App.UnitTests.csproj b/src/Framework/test/Microsoft.AspNetCore.App.UnitTests.csproj index 8cde3262d9..9b5fd65bc0 100644 --- a/src/Framework/test/Microsoft.AspNetCore.App.UnitTests.csproj +++ b/src/Framework/test/Microsoft.AspNetCore.App.UnitTests.csproj @@ -5,6 +5,8 @@ Microsoft.AspNetCore false + + true diff --git a/src/ProjectTemplates/scripts/Run-Angular-Locally.ps1 b/src/ProjectTemplates/scripts/Run-Angular-Locally.ps1 index a807e5260c..ec8993da7e 100644 --- a/src/ProjectTemplates/scripts/Run-Angular-Locally.ps1 +++ b/src/ProjectTemplates/scripts/Run-Angular-Locally.ps1 @@ -9,4 +9,4 @@ $ErrorActionPreference = 'Stop' . $PSScriptRoot\Test-Template.ps1 -Test-Template "angular" "angular" "Microsoft.DotNet.Web.Spa.ProjectTemplates.3.0.3.0.0-dev.nupkg" $true +Test-Template "angular" "angular" "Microsoft.DotNet.Web.Spa.ProjectTemplates.5.0.5.0.0-dev.nupkg" $true diff --git a/src/ProjectTemplates/scripts/Run-Blazor-Locally.ps1 b/src/ProjectTemplates/scripts/Run-Blazor-Locally.ps1 index a7062f82a5..575036e9c7 100644 --- a/src/ProjectTemplates/scripts/Run-Blazor-Locally.ps1 +++ b/src/ProjectTemplates/scripts/Run-Blazor-Locally.ps1 @@ -10,4 +10,4 @@ $ErrorActionPreference = 'Stop' . $PSScriptRoot\Test-Template.ps1 -Test-Template "blazorserver" "blazorserver" "Microsoft.DotNet.Web.ProjectTemplates.3.0.3.0.0-dev.nupkg" $false +Test-Template "blazorserver" "blazorserver" "Microsoft.DotNet.Web.ProjectTemplates.5.0.5.0.0-dev.nupkg" $false diff --git a/src/ProjectTemplates/scripts/Run-EmptyWeb-Locally.ps1 b/src/ProjectTemplates/scripts/Run-EmptyWeb-Locally.ps1 index b5684535cc..d6859c6f72 100644 --- a/src/ProjectTemplates/scripts/Run-EmptyWeb-Locally.ps1 +++ b/src/ProjectTemplates/scripts/Run-EmptyWeb-Locally.ps1 @@ -9,4 +9,4 @@ $ErrorActionPreference = 'Stop' . $PSScriptRoot\Test-Template.ps1 -Test-Template "web" "web" "Microsoft.DotNet.Web.ProjectTemplates.3.0.3.0.0-dev.nupkg" $false +Test-Template "web" "web" "Microsoft.DotNet.Web.ProjectTemplates.5.0.5.0.0-dev.nupkg" $false diff --git a/src/ProjectTemplates/scripts/Run-Razor-Locally.ps1 b/src/ProjectTemplates/scripts/Run-Razor-Locally.ps1 index 72f84ad9cc..ecba4fbd8f 100644 --- a/src/ProjectTemplates/scripts/Run-Razor-Locally.ps1 +++ b/src/ProjectTemplates/scripts/Run-Razor-Locally.ps1 @@ -6,4 +6,4 @@ param() . $PSScriptRoot\Test-Template.ps1 -Test-Template "webapp" "webapp -au Individual" "Microsoft.DotNet.Web.ProjectTemplates.3.0.3.0.0-dev.nupkg" $false +Test-Template "webapp" "webapp -au Individual" "Microsoft.DotNet.Web.ProjectTemplates.5.0.5.0.0-dev.nupkg" $false diff --git a/src/ProjectTemplates/scripts/Run-React-Locally.ps1 b/src/ProjectTemplates/scripts/Run-React-Locally.ps1 index 28bca93b11..8308860edc 100644 --- a/src/ProjectTemplates/scripts/Run-React-Locally.ps1 +++ b/src/ProjectTemplates/scripts/Run-React-Locally.ps1 @@ -9,4 +9,4 @@ $ErrorActionPreference = 'Stop' . $PSScriptRoot\Test-Template.ps1 -Test-Template "react" "react" "Microsoft.DotNet.Web.Spa.ProjectTemplates.3.0.3.0.0-dev.nupkg" $true +Test-Template "react" "react" "Microsoft.DotNet.Web.Spa.ProjectTemplates.5.0.5.0.0-dev.nupkg" $true diff --git a/src/ProjectTemplates/scripts/Run-ReactRedux-Locally.ps1 b/src/ProjectTemplates/scripts/Run-ReactRedux-Locally.ps1 index 19c1621dde..6100d7cacd 100644 --- a/src/ProjectTemplates/scripts/Run-ReactRedux-Locally.ps1 +++ b/src/ProjectTemplates/scripts/Run-ReactRedux-Locally.ps1 @@ -9,4 +9,4 @@ $ErrorActionPreference = 'Stop' . $PSScriptRoot\Test-Template.ps1 -Test-Template "reactredux" "reactredux" "Microsoft.DotNet.Web.Spa.ProjectTemplates.3.0.3.0.0-dev.nupkg" $true +Test-Template "reactredux" "reactredux" "Microsoft.DotNet.Web.Spa.ProjectTemplates.5.0.5.0.0-dev.nupkg" $true diff --git a/src/ProjectTemplates/scripts/Run-Starterweb-Locally.ps1 b/src/ProjectTemplates/scripts/Run-Starterweb-Locally.ps1 index c7adbbbf24..61ad47fbdc 100644 --- a/src/ProjectTemplates/scripts/Run-Starterweb-Locally.ps1 +++ b/src/ProjectTemplates/scripts/Run-Starterweb-Locally.ps1 @@ -9,4 +9,4 @@ $ErrorActionPreference = 'Stop' . $PSScriptRoot\Test-Template.ps1 -Test-Template "mvc" "mvc -au Individual" "Microsoft.DotNet.Web.ProjectTemplates.3.0.3.0.0-dev.nupkg" $false +Test-Template "mvc" "mvc -au Individual" "Microsoft.DotNet.Web.ProjectTemplates.5.0.5.0.0-dev.nupkg" $false diff --git a/src/ProjectTemplates/scripts/Run-Worker-Locally.ps1 b/src/ProjectTemplates/scripts/Run-Worker-Locally.ps1 index 867750b6e9..e6ff856e7e 100644 --- a/src/ProjectTemplates/scripts/Run-Worker-Locally.ps1 +++ b/src/ProjectTemplates/scripts/Run-Worker-Locally.ps1 @@ -9,4 +9,4 @@ $ErrorActionPreference = 'Stop' . $PSScriptRoot\Test-Template.ps1 -Test-Template "worker" "worker" "Microsoft.DotNet.Web.ProjectTemplates.3.0.3.0.0-dev.nupkg" $false +Test-Template "worker" "worker" "Microsoft.DotNet.Web.ProjectTemplates.5.0.5.0.0-dev.nupkg" $false diff --git a/src/ProjectTemplates/test/Helpers/TemplatePackageInstaller.cs b/src/ProjectTemplates/test/Helpers/TemplatePackageInstaller.cs index c36a9d5dda..b22d743ed0 100644 --- a/src/ProjectTemplates/test/Helpers/TemplatePackageInstaller.cs +++ b/src/ProjectTemplates/test/Helpers/TemplatePackageInstaller.cs @@ -31,9 +31,11 @@ namespace Templates.Test.Helpers "Microsoft.DotNet.Web.ProjectTemplates.2.1", "Microsoft.DotNet.Web.ProjectTemplates.2.2", "Microsoft.DotNet.Web.ProjectTemplates.3.0", + "Microsoft.DotNet.Web.ProjectTemplates.5.0", "Microsoft.DotNet.Web.Spa.ProjectTemplates.2.1", "Microsoft.DotNet.Web.Spa.ProjectTemplates.2.2", "Microsoft.DotNet.Web.Spa.ProjectTemplates.3.0", + "Microsoft.DotNet.Web.Spa.ProjectTemplates.5.0", "Microsoft.DotNet.Web.Spa.ProjectTemplates" }; diff --git a/src/SignalR/clients/csharp/Client.Core/src/Microsoft.AspNetCore.SignalR.Client.Core.csproj b/src/SignalR/clients/csharp/Client.Core/src/Microsoft.AspNetCore.SignalR.Client.Core.csproj index 736295fb4c..e287d4c869 100644 --- a/src/SignalR/clients/csharp/Client.Core/src/Microsoft.AspNetCore.SignalR.Client.Core.csproj +++ b/src/SignalR/clients/csharp/Client.Core/src/Microsoft.AspNetCore.SignalR.Client.Core.csproj @@ -32,7 +32,7 @@ - + diff --git a/src/SignalR/clients/ts/signalr-protocol-msgpack/package.json b/src/SignalR/clients/ts/signalr-protocol-msgpack/package.json index 3d262cc056..d53083aa27 100644 --- a/src/SignalR/clients/ts/signalr-protocol-msgpack/package.json +++ b/src/SignalR/clients/ts/signalr-protocol-msgpack/package.json @@ -1,6 +1,6 @@ { "name": "@microsoft/signalr-protocol-msgpack", - "version": "3.0.0-dev", + "version": "5.0.0-dev", "description": "MsgPack Protocol support for ASP.NET Core SignalR", "main": "./dist/cjs/index.js", "module": "./dist/esm/index.js", diff --git a/src/SignalR/clients/ts/signalr/package.json b/src/SignalR/clients/ts/signalr/package.json index 6b50f24d35..7fcfc9fb29 100644 --- a/src/SignalR/clients/ts/signalr/package.json +++ b/src/SignalR/clients/ts/signalr/package.json @@ -1,6 +1,6 @@ { "name": "@microsoft/signalr", - "version": "3.0.0-dev", + "version": "5.0.0-dev", "description": "ASP.NET Core SignalR Client", "main": "./dist/cjs/index.js", "module": "./dist/esm/index.js", diff --git a/src/SignalR/common/Http.Connections.Common/src/Microsoft.AspNetCore.Http.Connections.Common.csproj b/src/SignalR/common/Http.Connections.Common/src/Microsoft.AspNetCore.Http.Connections.Common.csproj index 3b037ab722..0af729a589 100644 --- a/src/SignalR/common/Http.Connections.Common/src/Microsoft.AspNetCore.Http.Connections.Common.csproj +++ b/src/SignalR/common/Http.Connections.Common/src/Microsoft.AspNetCore.Http.Connections.Common.csproj @@ -23,7 +23,7 @@ - + diff --git a/src/SignalR/common/Protocols.Json/src/Microsoft.AspNetCore.SignalR.Protocols.Json.csproj b/src/SignalR/common/Protocols.Json/src/Microsoft.AspNetCore.SignalR.Protocols.Json.csproj index 5904b5dc87..3ecd630e6d 100644 --- a/src/SignalR/common/Protocols.Json/src/Microsoft.AspNetCore.SignalR.Protocols.Json.csproj +++ b/src/SignalR/common/Protocols.Json/src/Microsoft.AspNetCore.SignalR.Protocols.Json.csproj @@ -22,7 +22,7 @@ - + diff --git a/src/SignalR/common/SignalR.Common/src/Microsoft.AspNetCore.SignalR.Common.csproj b/src/SignalR/common/SignalR.Common/src/Microsoft.AspNetCore.SignalR.Common.csproj index 118f42dc23..5d65e8da4b 100644 --- a/src/SignalR/common/SignalR.Common/src/Microsoft.AspNetCore.SignalR.Common.csproj +++ b/src/SignalR/common/SignalR.Common/src/Microsoft.AspNetCore.SignalR.Common.csproj @@ -28,7 +28,7 @@ - + diff --git a/src/SiteExtensions/LoggingAggregate/src/Microsoft.AspNetCore.AzureAppServices.SiteExtension/Microsoft.AspNetCore.AzureAppServices.SiteExtension.csproj b/src/SiteExtensions/LoggingAggregate/src/Microsoft.AspNetCore.AzureAppServices.SiteExtension/Microsoft.AspNetCore.AzureAppServices.SiteExtension.csproj index fe06164fdf..347eca2062 100644 --- a/src/SiteExtensions/LoggingAggregate/src/Microsoft.AspNetCore.AzureAppServices.SiteExtension/Microsoft.AspNetCore.AzureAppServices.SiteExtension.csproj +++ b/src/SiteExtensions/LoggingAggregate/src/Microsoft.AspNetCore.AzureAppServices.SiteExtension/Microsoft.AspNetCore.AzureAppServices.SiteExtension.csproj @@ -24,8 +24,8 @@ - - + + From 1bff37bec1b185217857cbdffd147283bb22cd76 Mon Sep 17 00:00:00 2001 From: bashdx Date: Tue, 30 Jul 2019 19:26:54 +0200 Subject: [PATCH 0130/1101] Issue #11559: Show meaningful error message for TLS over HTTP endpoint. (#12697) --- .../Core/src/BadHttpRequestException.cs | 3 + src/Servers/Kestrel/Core/src/CoreStrings.resx | 59 ++++++++++--------- .../Core/src/Internal/Http/HttpParser.cs | 23 +++++++- .../Internal/Http/RequestRejectionReason.cs | 3 +- .../Kestrel/Core/test/HttpParserTests.cs | 19 +++++- 5 files changed, 76 insertions(+), 31 deletions(-) diff --git a/src/Servers/Kestrel/Core/src/BadHttpRequestException.cs b/src/Servers/Kestrel/Core/src/BadHttpRequestException.cs index 929a408778..16f7ab0fce 100644 --- a/src/Servers/Kestrel/Core/src/BadHttpRequestException.cs +++ b/src/Servers/Kestrel/Core/src/BadHttpRequestException.cs @@ -139,6 +139,9 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core BadHttpRequestException ex; switch (reason) { + case RequestRejectionReason.TlsOverHttpError: + ex = new BadHttpRequestException(CoreStrings.HttpParserTlsOverHttpError, StatusCodes.Status400BadRequest, reason); + break; case RequestRejectionReason.InvalidRequestLine: ex = new BadHttpRequestException(CoreStrings.FormatBadRequest_InvalidRequestLine_Detail(detail), StatusCodes.Status400BadRequest, reason); break; diff --git a/src/Servers/Kestrel/Core/src/CoreStrings.resx b/src/Servers/Kestrel/Core/src/CoreStrings.resx index 7e7b2dbb56..0c415400aa 100644 --- a/src/Servers/Kestrel/Core/src/CoreStrings.resx +++ b/src/Servers/Kestrel/Core/src/CoreStrings.resx @@ -1,17 +1,17 @@ - @@ -614,4 +614,7 @@ For more information on configuring HTTPS see https://go.microsoft.com/fwlink/?l The HTTP/2 stream was reset by the application with error code {errorCode}. - + + Detected a TLS handshake to an endpoint that does not have TLS enabled. + + \ No newline at end of file diff --git a/src/Servers/Kestrel/Core/src/Internal/Http/HttpParser.cs b/src/Servers/Kestrel/Core/src/Internal/Http/HttpParser.cs index 2fe5dfdb36..ce63ec989f 100644 --- a/src/Servers/Kestrel/Core/src/Internal/Http/HttpParser.cs +++ b/src/Servers/Kestrel/Core/src/Internal/Http/HttpParser.cs @@ -31,6 +31,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http private const byte ByteTab = (byte)'\t'; private const byte ByteQuestionMark = (byte)'?'; private const byte BytePercentage = (byte)'%'; + private const int MinTlsRequestSize = 1; // We need at least 1 byte to check for a proper TLS request line public unsafe bool ParseRequestLine(TRequestHandler handler, in ReadOnlySequence buffer, out SequencePosition consumed, out SequencePosition examined) { @@ -415,9 +416,29 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http return new Span(data, methodLength); } + private unsafe bool IsTlsHandshake(byte* data, int length) + { + const byte SslRecordTypeHandshake = (byte)0x16; + + // Make sure we can check at least for the existence of a TLS handshake - we check the first byte + // See https://serializethoughts.com/2014/07/27/dissecting-tls-client-hello-message/ + + return (length >= MinTlsRequestSize && data[0] == SslRecordTypeHandshake); + } + [StackTraceHidden] private unsafe void RejectRequestLine(byte* requestLine, int length) - => throw GetInvalidRequestException(RequestRejectionReason.InvalidRequestLine, requestLine, length); + { + // Check for incoming TLS handshake over HTTP + if (IsTlsHandshake(requestLine, length)) + { + throw GetInvalidRequestException(RequestRejectionReason.TlsOverHttpError, requestLine, length); + } + else + { + throw GetInvalidRequestException(RequestRejectionReason.InvalidRequestLine, requestLine, length); + } + } [StackTraceHidden] private unsafe void RejectRequestHeader(byte* headerLine, int length) diff --git a/src/Servers/Kestrel/Core/src/Internal/Http/RequestRejectionReason.cs b/src/Servers/Kestrel/Core/src/Internal/Http/RequestRejectionReason.cs index fce21b6210..23dc6c67c6 100644 --- a/src/Servers/Kestrel/Core/src/Internal/Http/RequestRejectionReason.cs +++ b/src/Servers/Kestrel/Core/src/Internal/Http/RequestRejectionReason.cs @@ -1,10 +1,11 @@ -// Copyright (c) .NET Foundation. All rights reserved. +// 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. namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http { internal enum RequestRejectionReason { + TlsOverHttpError, UnrecognizedHTTPVersion, InvalidRequestLine, InvalidRequestHeader, diff --git a/src/Servers/Kestrel/Core/test/HttpParserTests.cs b/src/Servers/Kestrel/Core/test/HttpParserTests.cs index 7ce8587743..82d69d8b4d 100644 --- a/src/Servers/Kestrel/Core/test/HttpParserTests.cs +++ b/src/Servers/Kestrel/Core/test/HttpParserTests.cs @@ -1,4 +1,4 @@ -// Copyright (c) .NET Foundation. All rights reserved. +// 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; @@ -394,6 +394,23 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests Assert.Equal(buffer.End, examined); } + [Fact] + public void ParseRequestLineTlsOverHttp() + { + var parser = CreateParser(_nullTrace); + var buffer = ReadOnlySequenceFactory.CreateSegments(new byte[] { 0x16, 0x03, 0x01, 0x02, 0x00, 0x01, 0x00, 0xfc, 0x03, 0x03, 0x03, 0xca, 0xe0, 0xfd, 0x0a }); + + var requestHandler = new RequestHandler(); + + var badHttpRequestException = Assert.Throws(() => + { + parser.ParseRequestLine(requestHandler, buffer, out var consumed, out var examined); + }); + + Assert.Equal(badHttpRequestException.StatusCode, StatusCodes.Status400BadRequest); + Assert.Equal(RequestRejectionReason.TlsOverHttpError, badHttpRequestException.Reason); + } + [Fact] public void ParseHeadersWithGratuitouslySplitBuffers() { From c703093346c903fc05eb6108c0ef7b8a8205c95c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mateusz=20W=C3=B3jcik?= Date: Tue, 30 Jul 2019 19:55:00 +0200 Subject: [PATCH 0131/1101] Add option to disable adding trailing slash #2449 (#12669) Middlewares affected: - DefaultFilesMiddleware - DirectoryBrowserMiddleware --- ...ft.AspNetCore.StaticFiles.netcoreapp3.0.cs | 2 + .../StaticFiles/src/DefaultFilesMiddleware.cs | 10 +- .../src/DirectoryBrowserMiddleware.cs | 7 +- src/Middleware/StaticFiles/src/Helpers.cs | 23 +++- .../src/Infrastructure/SharedOptions.cs | 5 + .../src/Infrastructure/SharedOptionsBase.cs | 9 ++ .../UnitTests/DefaultFilesMiddlewareTests.cs | 90 ++++++++++++---- .../DirectoryBrowserMiddlewareTests.cs | 101 +++++++++++++----- 8 files changed, 189 insertions(+), 58 deletions(-) diff --git a/src/Middleware/StaticFiles/ref/Microsoft.AspNetCore.StaticFiles.netcoreapp3.0.cs b/src/Middleware/StaticFiles/ref/Microsoft.AspNetCore.StaticFiles.netcoreapp3.0.cs index 424801bb14..155d816cb8 100644 --- a/src/Middleware/StaticFiles/ref/Microsoft.AspNetCore.StaticFiles.netcoreapp3.0.cs +++ b/src/Middleware/StaticFiles/ref/Microsoft.AspNetCore.StaticFiles.netcoreapp3.0.cs @@ -120,12 +120,14 @@ namespace Microsoft.AspNetCore.StaticFiles.Infrastructure { public SharedOptions() { } public Microsoft.Extensions.FileProviders.IFileProvider FileProvider { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } + public bool RedirectToAppendTrailingSlash { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } public Microsoft.AspNetCore.Http.PathString RequestPath { get { throw null; } set { } } } public abstract partial class SharedOptionsBase { protected SharedOptionsBase(Microsoft.AspNetCore.StaticFiles.Infrastructure.SharedOptions sharedOptions) { } public Microsoft.Extensions.FileProviders.IFileProvider FileProvider { get { throw null; } set { } } + public bool RedirectToAppendTrailingSlash { get { throw null; } set { } } public Microsoft.AspNetCore.Http.PathString RequestPath { get { throw null; } set { } } protected Microsoft.AspNetCore.StaticFiles.Infrastructure.SharedOptions SharedOptions { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } } } diff --git a/src/Middleware/StaticFiles/src/DefaultFilesMiddleware.cs b/src/Middleware/StaticFiles/src/DefaultFilesMiddleware.cs index 3aabe2fe65..b3b4755789 100644 --- a/src/Middleware/StaticFiles/src/DefaultFilesMiddleware.cs +++ b/src/Middleware/StaticFiles/src/DefaultFilesMiddleware.cs @@ -80,17 +80,13 @@ namespace Microsoft.AspNetCore.StaticFiles { // If the path matches a directory but does not end in a slash, redirect to add the slash. // This prevents relative links from breaking. - if (!Helpers.PathEndsInSlash(context.Request.Path)) + if (!Helpers.PathEndsInSlash(context.Request.Path) && _options.RedirectToAppendTrailingSlash) { - context.Response.StatusCode = StatusCodes.Status301MovedPermanently; - var request = context.Request; - var redirect = UriHelper.BuildAbsolute(request.Scheme, request.Host, request.PathBase, request.Path + "/", request.QueryString); - context.Response.Headers[HeaderNames.Location] = redirect; + Helpers.RedirectToPathWithSlash(context); return Task.CompletedTask; } - // Match found, re-write the url. A later middleware will actually serve the file. - context.Request.Path = new PathString(context.Request.Path.Value + defaultFile); + context.Request.Path = new PathString(Helpers.GetPathValueWithSlash(context.Request.Path) + defaultFile); break; } } diff --git a/src/Middleware/StaticFiles/src/DirectoryBrowserMiddleware.cs b/src/Middleware/StaticFiles/src/DirectoryBrowserMiddleware.cs index 2d0a07b509..e689b309e4 100644 --- a/src/Middleware/StaticFiles/src/DirectoryBrowserMiddleware.cs +++ b/src/Middleware/StaticFiles/src/DirectoryBrowserMiddleware.cs @@ -87,12 +87,9 @@ namespace Microsoft.AspNetCore.StaticFiles { // If the path matches a directory but does not end in a slash, redirect to add the slash. // This prevents relative links from breaking. - if (!Helpers.PathEndsInSlash(context.Request.Path)) + if (!Helpers.PathEndsInSlash(context.Request.Path) && _options.RedirectToAppendTrailingSlash) { - context.Response.StatusCode = StatusCodes.Status301MovedPermanently; - var request = context.Request; - var redirect = UriHelper.BuildAbsolute(request.Scheme, request.Host, request.PathBase, request.Path + "/", request.QueryString); - context.Response.Headers[HeaderNames.Location] = redirect; + Helpers.RedirectToPathWithSlash(context); return Task.CompletedTask; } diff --git a/src/Middleware/StaticFiles/src/Helpers.cs b/src/Middleware/StaticFiles/src/Helpers.cs index a7a49e9070..d9b29c082f 100644 --- a/src/Middleware/StaticFiles/src/Helpers.cs +++ b/src/Middleware/StaticFiles/src/Helpers.cs @@ -2,9 +2,12 @@ // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; +using System.Threading.Tasks; using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Http.Extensions; using Microsoft.Extensions.FileProviders; +using Microsoft.Net.Http.Headers; namespace Microsoft.AspNetCore.StaticFiles { @@ -12,7 +15,8 @@ namespace Microsoft.AspNetCore.StaticFiles { internal static IFileProvider ResolveFileProvider(IWebHostEnvironment hostingEnv) { - if (hostingEnv.WebRootFileProvider == null) { + if (hostingEnv.WebRootFileProvider == null) + { throw new InvalidOperationException("Missing FileProvider."); } return hostingEnv.WebRootFileProvider; @@ -28,6 +32,23 @@ namespace Microsoft.AspNetCore.StaticFiles return path.Value.EndsWith("/", StringComparison.Ordinal); } + internal static string GetPathValueWithSlash(PathString path) + { + if (!PathEndsInSlash(path)) + { + return path.Value + "/"; + } + return path.Value; + } + + internal static void RedirectToPathWithSlash(HttpContext context) + { + context.Response.StatusCode = StatusCodes.Status301MovedPermanently; + var request = context.Request; + var redirect = UriHelper.BuildAbsolute(request.Scheme, request.Host, request.PathBase, request.Path + "/", request.QueryString); + context.Response.Headers[HeaderNames.Location] = redirect; + } + internal static bool TryMatchPath(HttpContext context, PathString matchUrl, bool forDirectory, out PathString subpath) { var path = context.Request.Path; diff --git a/src/Middleware/StaticFiles/src/Infrastructure/SharedOptions.cs b/src/Middleware/StaticFiles/src/Infrastructure/SharedOptions.cs index 1c1cc80ad5..d6f08129a1 100644 --- a/src/Middleware/StaticFiles/src/Infrastructure/SharedOptions.cs +++ b/src/Middleware/StaticFiles/src/Infrastructure/SharedOptions.cs @@ -42,5 +42,10 @@ namespace Microsoft.AspNetCore.StaticFiles.Infrastructure /// The file system used to locate resources /// public IFileProvider FileProvider { get; set; } + + /// + /// Indicates whether to redirect to add a trailing slash at the end of path. Relative resource links may require this. + /// + public bool RedirectToAppendTrailingSlash { get; set; } = true; } } diff --git a/src/Middleware/StaticFiles/src/Infrastructure/SharedOptionsBase.cs b/src/Middleware/StaticFiles/src/Infrastructure/SharedOptionsBase.cs index 16900ec6fb..9e41b96cdc 100644 --- a/src/Middleware/StaticFiles/src/Infrastructure/SharedOptionsBase.cs +++ b/src/Middleware/StaticFiles/src/Infrastructure/SharedOptionsBase.cs @@ -48,5 +48,14 @@ namespace Microsoft.AspNetCore.StaticFiles.Infrastructure get { return SharedOptions.FileProvider; } set { SharedOptions.FileProvider = value; } } + + /// + /// Indicates whether to redirect to add a trailing slash at the end of path. Relative resource links may require this. + /// + public bool RedirectToAppendTrailingSlash + { + get { return SharedOptions.RedirectToAppendTrailingSlash; } + set { SharedOptions.RedirectToAppendTrailingSlash = value; } + } } } diff --git a/src/Middleware/StaticFiles/test/UnitTests/DefaultFilesMiddlewareTests.cs b/src/Middleware/StaticFiles/test/UnitTests/DefaultFilesMiddlewareTests.cs index ed6947593b..9207ae844d 100644 --- a/src/Middleware/StaticFiles/test/UnitTests/DefaultFilesMiddlewareTests.cs +++ b/src/Middleware/StaticFiles/test/UnitTests/DefaultFilesMiddlewareTests.cs @@ -38,9 +38,14 @@ namespace Microsoft.AspNetCore.StaticFiles [InlineData("/subdir", @".", "/subdir/missing.dir")] [InlineData("/subdir", @".", "/subdir/missing.dir/")] [InlineData("", @"./", "/missing.dir")] - public async Task NoMatch_PassesThrough_All(string baseUrl, string baseDir, string requestUrl) + [InlineData("", @".", "/missing.dir", false)] + [InlineData("", @".", "/missing.dir/", false)] + [InlineData("/subdir", @".", "/subdir/missing.dir", false)] + [InlineData("/subdir", @".", "/subdir/missing.dir/", false)] + [InlineData("", @"./", "/missing.dir", false)] + public async Task NoMatch_PassesThrough_All(string baseUrl, string baseDir, string requestUrl, bool appendTrailingSlash = true) { - await NoMatch_PassesThrough(baseUrl, baseDir, requestUrl); + await NoMatch_PassesThrough(baseUrl, baseDir, requestUrl, appendTrailingSlash); } [ConditionalTheory] @@ -48,12 +53,14 @@ namespace Microsoft.AspNetCore.StaticFiles [OSSkipCondition(OperatingSystems.MacOSX)] [InlineData("", @".\", "/missing.dir")] [InlineData("", @".\", "/Missing.dir")] - public async Task NoMatch_PassesThrough_Windows(string baseUrl, string baseDir, string requestUrl) + [InlineData("", @".\", "/missing.dir", false)] + [InlineData("", @".\", "/Missing.dir", false)] + public async Task NoMatch_PassesThrough_Windows(string baseUrl, string baseDir, string requestUrl, bool appendTrailingSlash = true) { - await NoMatch_PassesThrough(baseUrl, baseDir, requestUrl); + await NoMatch_PassesThrough(baseUrl, baseDir, requestUrl, appendTrailingSlash); } - private async Task NoMatch_PassesThrough(string baseUrl, string baseDir, string requestUrl) + private async Task NoMatch_PassesThrough(string baseUrl, string baseDir, string requestUrl, bool appendTrailingSlash = true) { using (var fileProvider = new PhysicalFileProvider(Path.Combine(AppContext.BaseDirectory, baseDir))) { @@ -62,7 +69,8 @@ namespace Microsoft.AspNetCore.StaticFiles app.UseDefaultFiles(new DefaultFilesOptions { RequestPath = new PathString(baseUrl), - FileProvider = fileProvider + FileProvider = fileProvider, + RedirectToAppendTrailingSlash = appendTrailingSlash }); app.Run(context => context.Response.WriteAsync(context.Request.Path.Value)); }); @@ -102,7 +110,7 @@ namespace Microsoft.AspNetCore.StaticFiles FileProvider = fileProvider }); - app.UseEndpoints(endpoints => {}); + app.UseEndpoints(endpoints => { }); }, services => { services.AddDirectoryBrowser(); services.AddRouting(); }); @@ -118,9 +126,19 @@ namespace Microsoft.AspNetCore.StaticFiles [InlineData("", @"./SubFolder", "/")] [InlineData("", @"./SubFolder", "/你好/")] [InlineData("", @"./SubFolder", "/你好/世界/")] - public async Task FoundDirectoryWithDefaultFile_PathModified_All(string baseUrl, string baseDir, string requestUrl) + [InlineData("", @".", "/SubFolder/", false)] + [InlineData("", @"./", "/SubFolder/", false)] + [InlineData("", @"./SubFolder", "/", false)] + [InlineData("", @"./SubFolder", "/你好/", false)] + [InlineData("", @"./SubFolder", "/你好/世界/", false)] + [InlineData("", @".", "/SubFolder", false)] + [InlineData("", @"./", "/SubFolder", false)] + [InlineData("", @"./SubFolder", "", false)] + [InlineData("", @"./SubFolder", "/你好", false)] + [InlineData("", @"./SubFolder", "/你好/世界", false)] + public async Task FoundDirectoryWithDefaultFile_PathModified_All(string baseUrl, string baseDir, string requestUrl, bool appendTrailingSlash = true) { - await FoundDirectoryWithDefaultFile_PathModified(baseUrl, baseDir, requestUrl); + await FoundDirectoryWithDefaultFile_PathModified(baseUrl, baseDir, requestUrl, appendTrailingSlash); } [ConditionalTheory] @@ -130,12 +148,20 @@ namespace Microsoft.AspNetCore.StaticFiles [InlineData("", @".\subFolder", "/")] [InlineData("", @".\SubFolder", "/你好/")] [InlineData("", @".\SubFolder", "/你好/世界/")] - public async Task FoundDirectoryWithDefaultFile_PathModified_Windows(string baseUrl, string baseDir, string requestUrl) + [InlineData("", @".\", "/SubFolder/", false)] + [InlineData("", @".\subFolder", "/", false)] + [InlineData("", @".\SubFolder", "/你好/", false)] + [InlineData("", @".\SubFolder", "/你好/世界/", false)] + [InlineData("", @".\", "/SubFolder", false)] + [InlineData("", @".\subFolder", "", false)] + [InlineData("", @".\SubFolder", "/你好", false)] + [InlineData("", @".\SubFolder", "/你好/世界", false)] + public async Task FoundDirectoryWithDefaultFile_PathModified_Windows(string baseUrl, string baseDir, string requestUrl, bool appendTrailingSlash = true) { - await FoundDirectoryWithDefaultFile_PathModified(baseUrl, baseDir, requestUrl); + await FoundDirectoryWithDefaultFile_PathModified(baseUrl, baseDir, requestUrl, appendTrailingSlash); } - private async Task FoundDirectoryWithDefaultFile_PathModified(string baseUrl, string baseDir, string requestUrl) + private async Task FoundDirectoryWithDefaultFile_PathModified(string baseUrl, string baseDir, string requestUrl, bool appendTrailingSlash = true) { using (var fileProvider = new PhysicalFileProvider(Path.Combine(AppContext.BaseDirectory, baseDir))) { @@ -144,14 +170,17 @@ namespace Microsoft.AspNetCore.StaticFiles app.UseDefaultFiles(new DefaultFilesOptions { RequestPath = new PathString(baseUrl), - FileProvider = fileProvider + FileProvider = fileProvider, + RedirectToAppendTrailingSlash = appendTrailingSlash }); app.Run(context => context.Response.WriteAsync(context.Request.Path.Value)); }); var response = await server.CreateClient().GetAsync(requestUrl); + Assert.Equal(HttpStatusCode.OK, response.StatusCode); - Assert.Equal(requestUrl + "default.html", await response.Content.ReadAsStringAsync()); // Should be modified + var requestUrlWithSlash = requestUrl.EndsWith("/") ? requestUrl : requestUrl + "/"; + Assert.Equal(requestUrlWithSlash + "default.html", await response.Content.ReadAsStringAsync()); // Should be modified and be valid path to file } } @@ -202,9 +231,17 @@ namespace Microsoft.AspNetCore.StaticFiles [InlineData("/SubFolder", @".", "/somedir/")] [InlineData("", @"./SubFolder", "/")] [InlineData("", @"./SubFolder/", "/")] - public async Task PostDirectory_PassesThrough_All(string baseUrl, string baseDir, string requestUrl) + [InlineData("/SubFolder", @"./", "/SubFolder/", false)] + [InlineData("/SubFolder", @".", "/somedir/", false)] + [InlineData("", @"./SubFolder", "/", false)] + [InlineData("", @"./SubFolder/", "/", false)] + [InlineData("/SubFolder", @"./", "/SubFolder", false)] + [InlineData("/SubFolder", @".", "/somedir", false)] + [InlineData("", @"./SubFolder", "", false)] + [InlineData("", @"./SubFolder/", "", false)] + public async Task PostDirectory_PassesThrough_All(string baseUrl, string baseDir, string requestUrl, bool appendTrailingSlash = true) { - await PostDirectory_PassesThrough(baseUrl, baseDir, requestUrl); + await PostDirectory_PassesThrough(baseUrl, baseDir, requestUrl, appendTrailingSlash); } [ConditionalTheory] @@ -213,24 +250,37 @@ namespace Microsoft.AspNetCore.StaticFiles [InlineData("/SubFolder", @".\", "/SubFolder/")] [InlineData("", @".\SubFolder", "/")] [InlineData("", @".\SubFolder\", "/")] - public async Task PostDirectory_PassesThrough_Windows(string baseUrl, string baseDir, string requestUrl) + [InlineData("/SubFolder", @".\", "/SubFolder/", false)] + [InlineData("", @".\SubFolder", "/", false)] + [InlineData("", @".\SubFolder\", "/", false)] + [InlineData("/SubFolder", @".\", "/SubFolder", false)] + [InlineData("", @".\SubFolder", "", false)] + [InlineData("", @".\SubFolder\", "", false)] + public async Task PostDirectory_PassesThrough_Windows(string baseUrl, string baseDir, string requestUrl, bool appendTrailingSlash = true) { - await PostDirectory_PassesThrough(baseUrl, baseDir, requestUrl); + await PostDirectory_PassesThrough(baseUrl, baseDir, requestUrl, appendTrailingSlash); } - private async Task PostDirectory_PassesThrough(string baseUrl, string baseDir, string requestUrl) + private async Task PostDirectory_PassesThrough(string baseUrl, string baseDir, string requestUrl, bool appendTrailingSlash = true) { using (var fileProvider = new PhysicalFileProvider(Path.Combine(AppContext.BaseDirectory, baseDir))) { var server = StaticFilesTestServer.Create(app => app.UseDefaultFiles(new DefaultFilesOptions { RequestPath = new PathString(baseUrl), - FileProvider = fileProvider + FileProvider = fileProvider, + RedirectToAppendTrailingSlash = appendTrailingSlash })); var response = await server.CreateRequest(requestUrl).GetAsync(); Assert.Equal(HttpStatusCode.NotFound, response.StatusCode); // Passed through } } + + [Fact] + public void Options_AppendTrailingSlashByDefault() + { + Assert.True(new DefaultFilesOptions().RedirectToAppendTrailingSlash); + } } } diff --git a/src/Middleware/StaticFiles/test/UnitTests/DirectoryBrowserMiddlewareTests.cs b/src/Middleware/StaticFiles/test/UnitTests/DirectoryBrowserMiddlewareTests.cs index 3bbdabfe30..8cf6d0c34c 100644 --- a/src/Middleware/StaticFiles/test/UnitTests/DirectoryBrowserMiddlewareTests.cs +++ b/src/Middleware/StaticFiles/test/UnitTests/DirectoryBrowserMiddlewareTests.cs @@ -56,9 +56,14 @@ namespace Microsoft.AspNetCore.StaticFiles [InlineData("/subdir", @".", "/subdir/missing.dir")] [InlineData("/subdir", @".", "/subdir/missing.dir/")] [InlineData("", @"./", "/missing.dir")] - public async Task NoMatch_PassesThrough_All(string baseUrl, string baseDir, string requestUrl) + [InlineData("", @".", "/missing.dir", false)] + [InlineData("", @".", "/missing.dir/", false)] + [InlineData("/subdir", @".", "/subdir/missing.dir", false)] + [InlineData("/subdir", @".", "/subdir/missing.dir/", false)] + [InlineData("", @"./", "/missing.dir", false)] + public async Task NoMatch_PassesThrough_All(string baseUrl, string baseDir, string requestUrl, bool appendTrailingSlash = true) { - await NoMatch_PassesThrough(baseUrl, baseDir, requestUrl); + await NoMatch_PassesThrough(baseUrl, baseDir, requestUrl, appendTrailingSlash); } [ConditionalTheory] @@ -66,12 +71,14 @@ namespace Microsoft.AspNetCore.StaticFiles [OSSkipCondition(OperatingSystems.MacOSX)] [InlineData("", @".\", "/missing.dir")] [InlineData("", @".\", "/Missing.dir")] - public async Task NoMatch_PassesThrough_Windows(string baseUrl, string baseDir, string requestUrl) + [InlineData("", @".\", "/missing.dir", false)] + [InlineData("", @".\", "/Missing.dir", false)] + public async Task NoMatch_PassesThrough_Windows(string baseUrl, string baseDir, string requestUrl, bool appendTrailingSlash = true) { - await NoMatch_PassesThrough(baseUrl, baseDir, requestUrl); + await NoMatch_PassesThrough(baseUrl, baseDir, requestUrl, appendTrailingSlash); } - private async Task NoMatch_PassesThrough(string baseUrl, string baseDir, string requestUrl) + private async Task NoMatch_PassesThrough(string baseUrl, string baseDir, string requestUrl, bool appendTrailingSlash = true) { using (var fileProvider = new PhysicalFileProvider(Path.Combine(AppContext.BaseDirectory, baseDir))) { @@ -79,7 +86,8 @@ namespace Microsoft.AspNetCore.StaticFiles app => app.UseDirectoryBrowser(new DirectoryBrowserOptions { RequestPath = new PathString(baseUrl), - FileProvider = fileProvider + FileProvider = fileProvider, + RedirectToAppendTrailingSlash = appendTrailingSlash }), services => services.AddDirectoryBrowser()); var response = await server.CreateRequest(requestUrl).GetAsync(); @@ -117,7 +125,7 @@ namespace Microsoft.AspNetCore.StaticFiles FileProvider = fileProvider }); - app.UseEndpoints(endpoints => {}); + app.UseEndpoints(endpoints => { }); }, services => { services.AddDirectoryBrowser(); services.AddRouting(); }); @@ -133,9 +141,19 @@ namespace Microsoft.AspNetCore.StaticFiles [InlineData("/somedir", @".", "/somedir/")] [InlineData("/somedir", @"./", "/somedir/")] [InlineData("/somedir", @".", "/somedir/SubFolder/")] - public async Task FoundDirectory_Served_All(string baseUrl, string baseDir, string requestUrl) + [InlineData("", @".", "/", false)] + [InlineData("", @".", "/SubFolder/", false)] + [InlineData("/somedir", @".", "/somedir/", false)] + [InlineData("/somedir", @"./", "/somedir/", false)] + [InlineData("/somedir", @".", "/somedir/SubFolder/", false)] + [InlineData("", @".", "", false)] + [InlineData("", @".", "/SubFolder", false)] + [InlineData("/somedir", @".", "/somedir", false)] + [InlineData("/somedir", @"./", "/somedir", false)] + [InlineData("/somedir", @".", "/somedir/SubFolder", false)] + public async Task FoundDirectory_Served_All(string baseUrl, string baseDir, string requestUrl, bool appendTrailingSlash = true) { - await FoundDirectory_Served(baseUrl, baseDir, requestUrl); + await FoundDirectory_Served(baseUrl, baseDir, requestUrl, appendTrailingSlash); } [ConditionalTheory] @@ -143,12 +161,16 @@ namespace Microsoft.AspNetCore.StaticFiles [OSSkipCondition(OperatingSystems.MacOSX)] [InlineData("/somedir", @".\", "/somedir/")] [InlineData("/somedir", @".", "/somedir/subFolder/")] - public async Task FoundDirectory_Served_Windows(string baseUrl, string baseDir, string requestUrl) + [InlineData("/somedir", @".\", "/somedir/", false)] + [InlineData("/somedir", @".", "/somedir/subFolder/", false)] + [InlineData("/somedir", @".\", "/somedir", false)] + [InlineData("/somedir", @".", "/somedir/subFolder", false)] + public async Task FoundDirectory_Served_Windows(string baseUrl, string baseDir, string requestUrl, bool appendTrailingSlash = true) { - await FoundDirectory_Served(baseUrl, baseDir, requestUrl); + await FoundDirectory_Served(baseUrl, baseDir, requestUrl, appendTrailingSlash); } - private async Task FoundDirectory_Served(string baseUrl, string baseDir, string requestUrl) + private async Task FoundDirectory_Served(string baseUrl, string baseDir, string requestUrl, bool appendTrailingSlash = true) { using (var fileProvider = new PhysicalFileProvider(Path.Combine(AppContext.BaseDirectory, baseDir))) { @@ -156,7 +178,8 @@ namespace Microsoft.AspNetCore.StaticFiles app => app.UseDirectoryBrowser(new DirectoryBrowserOptions { RequestPath = new PathString(baseUrl), - FileProvider = fileProvider + FileProvider = fileProvider, + RedirectToAppendTrailingSlash = appendTrailingSlash, }), services => services.AddDirectoryBrowser()); var response = await server.CreateRequest(requestUrl).GetAsync(); @@ -215,21 +238,31 @@ namespace Microsoft.AspNetCore.StaticFiles [InlineData("", @".", "/SubFolder/")] [InlineData("/somedir", @".", "/somedir/")] [InlineData("/somedir", @".", "/somedir/SubFolder/")] - public async Task PostDirectory_PassesThrough_All(string baseUrl, string baseDir, string requestUrl) + [InlineData("", @".", "/", false)] + [InlineData("", @".", "/SubFolder/", false)] + [InlineData("/somedir", @".", "/somedir/", false)] + [InlineData("/somedir", @".", "/somedir/SubFolder/", false)] + [InlineData("", @".", "", false)] + [InlineData("", @".", "/SubFolder", false)] + [InlineData("/somedir", @".", "/somedir", false)] + [InlineData("/somedir", @".", "/somedir/SubFolder", false)] + public async Task PostDirectory_PassesThrough_All(string baseUrl, string baseDir, string requestUrl, bool appendTrailingSlash = true) { - await PostDirectory_PassesThrough(baseUrl, baseDir, requestUrl); + await PostDirectory_PassesThrough(baseUrl, baseDir, requestUrl, appendTrailingSlash); } [ConditionalTheory] [OSSkipCondition(OperatingSystems.Linux)] [OSSkipCondition(OperatingSystems.MacOSX)] [InlineData("/somedir", @".", "/somedir/subFolder/")] - public async Task PostDirectory_PassesThrough_Windows(string baseUrl, string baseDir, string requestUrl) + [InlineData("/somedir", @".", "/somedir/subFolder/", false)] + [InlineData("/somedir", @".", "/somedir/subFolder", false)] + public async Task PostDirectory_PassesThrough_Windows(string baseUrl, string baseDir, string requestUrl, bool appendTrailingSlash = true) { - await PostDirectory_PassesThrough(baseUrl, baseDir, requestUrl); + await PostDirectory_PassesThrough(baseUrl, baseDir, requestUrl, appendTrailingSlash); } - private async Task PostDirectory_PassesThrough(string baseUrl, string baseDir, string requestUrl) + private async Task PostDirectory_PassesThrough(string baseUrl, string baseDir, string requestUrl, bool appendTrailingSlash = true) { using (var fileProvider = new PhysicalFileProvider(Path.Combine(AppContext.BaseDirectory, baseDir))) { @@ -237,7 +270,8 @@ namespace Microsoft.AspNetCore.StaticFiles app => app.UseDirectoryBrowser(new DirectoryBrowserOptions { RequestPath = new PathString(baseUrl), - FileProvider = fileProvider + FileProvider = fileProvider, + RedirectToAppendTrailingSlash = appendTrailingSlash }), services => services.AddDirectoryBrowser()); @@ -251,21 +285,31 @@ namespace Microsoft.AspNetCore.StaticFiles [InlineData("", @".", "/SubFolder/")] [InlineData("/somedir", @".", "/somedir/")] [InlineData("/somedir", @".", "/somedir/SubFolder/")] - public async Task HeadDirectory_HeadersButNotBodyServed_All(string baseUrl, string baseDir, string requestUrl) + [InlineData("", @".", "/", false)] + [InlineData("", @".", "/SubFolder/", false)] + [InlineData("/somedir", @".", "/somedir/", false)] + [InlineData("/somedir", @".", "/somedir/SubFolder/", false)] + [InlineData("", @".", "", false)] + [InlineData("", @".", "/SubFolder", false)] + [InlineData("/somedir", @".", "/somedir", false)] + [InlineData("/somedir", @".", "/somedir/SubFolder", false)] + public async Task HeadDirectory_HeadersButNotBodyServed_All(string baseUrl, string baseDir, string requestUrl, bool appendTrailingSlash = true) { - await HeadDirectory_HeadersButNotBodyServed(baseUrl, baseDir, requestUrl); + await HeadDirectory_HeadersButNotBodyServed(baseUrl, baseDir, requestUrl, appendTrailingSlash); } [ConditionalTheory] [OSSkipCondition(OperatingSystems.Linux)] [OSSkipCondition(OperatingSystems.MacOSX)] [InlineData("/somedir", @".", "/somedir/subFolder/")] - public async Task HeadDirectory_HeadersButNotBodyServed_Windows(string baseUrl, string baseDir, string requestUrl) + [InlineData("/somedir", @".", "/somedir/subFolder/", false)] + [InlineData("/somedir", @".", "/somedir/subFolder", false)] + public async Task HeadDirectory_HeadersButNotBodyServed_Windows(string baseUrl, string baseDir, string requestUrl, bool appendTrailingSlash = true) { - await HeadDirectory_HeadersButNotBodyServed(baseUrl, baseDir, requestUrl); + await HeadDirectory_HeadersButNotBodyServed(baseUrl, baseDir, requestUrl, appendTrailingSlash); } - private async Task HeadDirectory_HeadersButNotBodyServed(string baseUrl, string baseDir, string requestUrl) + private async Task HeadDirectory_HeadersButNotBodyServed(string baseUrl, string baseDir, string requestUrl, bool appendTrailingSlash = true) { using (var fileProvider = new PhysicalFileProvider(Path.Combine(AppContext.BaseDirectory, baseDir))) { @@ -273,7 +317,8 @@ namespace Microsoft.AspNetCore.StaticFiles app => app.UseDirectoryBrowser(new DirectoryBrowserOptions { RequestPath = new PathString(baseUrl), - FileProvider = fileProvider + FileProvider = fileProvider, + RedirectToAppendTrailingSlash = appendTrailingSlash }), services => services.AddDirectoryBrowser()); @@ -285,5 +330,11 @@ namespace Microsoft.AspNetCore.StaticFiles Assert.Empty((await response.Content.ReadAsByteArrayAsync())); } } + + [Fact] + public void Options_AppendTrailingSlashByDefault() + { + Assert.True(new DirectoryBrowserOptions().RedirectToAppendTrailingSlash); + } } } From 751882cf3c45d0eb982f244d59bfc758b4b6c17b Mon Sep 17 00:00:00 2001 From: Justin Robb Date: Wed, 31 Jul 2019 12:56:08 -0700 Subject: [PATCH 0132/1101] Add optional packageManagerName to allow alternative package managers --- .../src/AngularCli/AngularCliBuilder.cs | 9 +++-- .../src/AngularCli/AngularCliMiddleware.cs | 9 ++--- ...NpmScriptRunner.cs => NodeScriptRunner.cs} | 33 +++++++++++-------- .../ReactDevelopmentServerMiddleware.cs | 9 ++--- ...ctDevelopmentServerMiddlewareExtensions.cs | 2 +- .../SpaServices.Extensions/src/SpaOptions.cs | 28 ++++++++++++++-- 6 files changed, 61 insertions(+), 29 deletions(-) rename src/Middleware/SpaServices.Extensions/src/Npm/{NpmScriptRunner.cs => NodeScriptRunner.cs} (77%) diff --git a/src/Middleware/SpaServices.Extensions/src/AngularCli/AngularCliBuilder.cs b/src/Middleware/SpaServices.Extensions/src/AngularCli/AngularCliBuilder.cs index 61dedd350a..0a6a320193 100644 --- a/src/Middleware/SpaServices.Extensions/src/AngularCli/AngularCliBuilder.cs +++ b/src/Middleware/SpaServices.Extensions/src/AngularCli/AngularCliBuilder.cs @@ -1,4 +1,4 @@ -// Copyright (c) .NET Foundation. All rights reserved. +// 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 Microsoft.AspNetCore.Builder; @@ -40,20 +40,23 @@ namespace Microsoft.AspNetCore.SpaServices.AngularCli /// public async Task Build(ISpaBuilder spaBuilder) { + var pkgManagerName = spaBuilder.Options.PackageManagerName; var sourcePath = spaBuilder.Options.SourcePath; if (string.IsNullOrEmpty(sourcePath)) { throw new InvalidOperationException($"To use {nameof(AngularCliBuilder)}, you must supply a non-empty value for the {nameof(SpaOptions.SourcePath)} property of {nameof(SpaOptions)} when calling {nameof(SpaApplicationBuilderExtensions.UseSpa)}."); } + var logger = LoggerFinder.GetOrCreateLogger( spaBuilder.ApplicationBuilder, nameof(AngularCliBuilder)); - var npmScriptRunner = new NpmScriptRunner( + var npmScriptRunner = new NodeScriptRunner( sourcePath, _npmScriptName, "--watch", - null); + null, + pkgManagerName); npmScriptRunner.AttachToLogger(logger); using (var stdOutReader = new EventedStreamStringReader(npmScriptRunner.StdOut)) diff --git a/src/Middleware/SpaServices.Extensions/src/AngularCli/AngularCliMiddleware.cs b/src/Middleware/SpaServices.Extensions/src/AngularCli/AngularCliMiddleware.cs index 9090f7738b..1f1ba8cdc1 100644 --- a/src/Middleware/SpaServices.Extensions/src/AngularCli/AngularCliMiddleware.cs +++ b/src/Middleware/SpaServices.Extensions/src/AngularCli/AngularCliMiddleware.cs @@ -25,6 +25,7 @@ namespace Microsoft.AspNetCore.SpaServices.AngularCli ISpaBuilder spaBuilder, string npmScriptName) { + var pkgManagerName = spaBuilder.Options.PackageManagerName; var sourcePath = spaBuilder.Options.SourcePath; if (string.IsNullOrEmpty(sourcePath)) { @@ -39,7 +40,7 @@ namespace Microsoft.AspNetCore.SpaServices.AngularCli // Start Angular CLI and attach to middleware pipeline var appBuilder = spaBuilder.ApplicationBuilder; var logger = LoggerFinder.GetOrCreateLogger(appBuilder, LogCategoryName); - var angularCliServerInfoTask = StartAngularCliServerAsync(sourcePath, npmScriptName, logger); + var angularCliServerInfoTask = StartAngularCliServerAsync(sourcePath, npmScriptName, pkgManagerName, logger); // Everything we proxy is hardcoded to target http://localhost because: // - the requests are always from the local machine (we're not accepting remote @@ -62,13 +63,13 @@ namespace Microsoft.AspNetCore.SpaServices.AngularCli } private static async Task StartAngularCliServerAsync( - string sourcePath, string npmScriptName, ILogger logger) + string sourcePath, string npmScriptName, string pkgManagerName, ILogger logger) { var portNumber = TcpPortFinder.FindAvailablePort(); logger.LogInformation($"Starting @angular/cli on port {portNumber}..."); - var npmScriptRunner = new NpmScriptRunner( - sourcePath, npmScriptName, $"--port {portNumber}", null); + var npmScriptRunner = new NodeScriptRunner( + sourcePath, npmScriptName, $"--port {portNumber}", null, pkgManagerName); npmScriptRunner.AttachToLogger(logger); Match openBrowserLine; diff --git a/src/Middleware/SpaServices.Extensions/src/Npm/NpmScriptRunner.cs b/src/Middleware/SpaServices.Extensions/src/Npm/NodeScriptRunner.cs similarity index 77% rename from src/Middleware/SpaServices.Extensions/src/Npm/NpmScriptRunner.cs rename to src/Middleware/SpaServices.Extensions/src/Npm/NodeScriptRunner.cs index 378ec5f9fa..86e6497f19 100644 --- a/src/Middleware/SpaServices.Extensions/src/Npm/NpmScriptRunner.cs +++ b/src/Middleware/SpaServices.Extensions/src/Npm/NodeScriptRunner.cs @@ -1,4 +1,4 @@ -// Copyright (c) .NET Foundation. All rights reserved. +// 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 Microsoft.Extensions.Logging; @@ -16,14 +16,14 @@ namespace Microsoft.AspNetCore.NodeServices.Npm /// Executes the script entries defined in a package.json file, /// capturing any output written to stdio. /// - internal class NpmScriptRunner + internal class NodeScriptRunner { public EventedStreamReader StdOut { get; } public EventedStreamReader StdErr { get; } private static Regex AnsiColorRegex = new Regex("\x001b\\[[0-9;]*m", RegexOptions.None, TimeSpan.FromSeconds(1)); - public NpmScriptRunner(string workingDirectory, string scriptName, string arguments, IDictionary envVars) + public NodeScriptRunner(string workingDirectory, string scriptName, string arguments, IDictionary envVars, string pkgManagerName) { if (string.IsNullOrEmpty(workingDirectory)) { @@ -35,18 +35,23 @@ namespace Microsoft.AspNetCore.NodeServices.Npm throw new ArgumentException("Cannot be null or empty.", nameof(scriptName)); } - var npmExe = "npm"; + if (string.IsNullOrEmpty(pkgManagerName)) + { + throw new ArgumentException("Cannot be null or empty.", nameof(pkgManagerName)); + } + + var exeToRun = pkgManagerName; var completeArguments = $"run {scriptName} -- {arguments ?? string.Empty}"; if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) { - // On Windows, the NPM executable is a .cmd file, so it can't be executed + // On Windows, the node executable is a .cmd file, so it can't be executed // directly (except with UseShellExecute=true, but that's no good, because // it prevents capturing stdio). So we need to invoke it via "cmd /c". - npmExe = "cmd"; - completeArguments = $"/c npm {completeArguments}"; + exeToRun = "cmd"; + completeArguments = $"/c {pkgManagerName} {completeArguments}"; } - var processStartInfo = new ProcessStartInfo(npmExe) + var processStartInfo = new ProcessStartInfo(exeToRun) { Arguments = completeArguments, UseShellExecute = false, @@ -64,19 +69,19 @@ namespace Microsoft.AspNetCore.NodeServices.Npm } } - var process = LaunchNodeProcess(processStartInfo); + var process = LaunchNodeProcess(processStartInfo, pkgManagerName); StdOut = new EventedStreamReader(process.StandardOutput); StdErr = new EventedStreamReader(process.StandardError); } public void AttachToLogger(ILogger logger) { - // When the NPM task emits complete lines, pass them through to the real logger + // When the node task emits complete lines, pass them through to the real logger StdOut.OnReceivedLine += line => { if (!string.IsNullOrWhiteSpace(line)) { - // NPM tasks commonly emit ANSI colors, but it wouldn't make sense to forward + // Node tasks commonly emit ANSI colors, but it wouldn't make sense to forward // those to loggers (because a logger isn't necessarily any kind of terminal) logger.LogInformation(StripAnsiColors(line)); } @@ -106,7 +111,7 @@ namespace Microsoft.AspNetCore.NodeServices.Npm private static string StripAnsiColors(string line) => AnsiColorRegex.Replace(line, string.Empty); - private static Process LaunchNodeProcess(ProcessStartInfo startInfo) + private static Process LaunchNodeProcess(ProcessStartInfo startInfo, string commandName) { try { @@ -119,8 +124,8 @@ namespace Microsoft.AspNetCore.NodeServices.Npm } catch (Exception ex) { - var message = $"Failed to start 'npm'. To resolve this:.\n\n" - + "[1] Ensure that 'npm' is installed and can be found in one of the PATH directories.\n" + var message = $"Failed to start '{commandName}'. To resolve this:.\n\n" + + $"[1] Ensure that '{commandName}' is installed and can be found in one of the PATH directories.\n" + $" Current PATH enviroment variable is: { Environment.GetEnvironmentVariable("PATH") }\n" + " Make sure the executable is in one of those directories, or update your PATH.\n\n" + "[2] See the InnerException for further details of the cause."; diff --git a/src/Middleware/SpaServices.Extensions/src/ReactDevelopmentServer/ReactDevelopmentServerMiddleware.cs b/src/Middleware/SpaServices.Extensions/src/ReactDevelopmentServer/ReactDevelopmentServerMiddleware.cs index 78a7b4f03f..4dfe28457d 100644 --- a/src/Middleware/SpaServices.Extensions/src/ReactDevelopmentServer/ReactDevelopmentServerMiddleware.cs +++ b/src/Middleware/SpaServices.Extensions/src/ReactDevelopmentServer/ReactDevelopmentServerMiddleware.cs @@ -24,6 +24,7 @@ namespace Microsoft.AspNetCore.SpaServices.ReactDevelopmentServer ISpaBuilder spaBuilder, string npmScriptName) { + var pkgManagerName = spaBuilder.Options.PackageManagerName; var sourcePath = spaBuilder.Options.SourcePath; if (string.IsNullOrEmpty(sourcePath)) { @@ -38,7 +39,7 @@ namespace Microsoft.AspNetCore.SpaServices.ReactDevelopmentServer // Start create-react-app and attach to middleware pipeline var appBuilder = spaBuilder.ApplicationBuilder; var logger = LoggerFinder.GetOrCreateLogger(appBuilder, LogCategoryName); - var portTask = StartCreateReactAppServerAsync(sourcePath, npmScriptName, logger); + var portTask = StartCreateReactAppServerAsync(sourcePath, npmScriptName, pkgManagerName, logger); // Everything we proxy is hardcoded to target http://localhost because: // - the requests are always from the local machine (we're not accepting remote @@ -61,7 +62,7 @@ namespace Microsoft.AspNetCore.SpaServices.ReactDevelopmentServer } private static async Task StartCreateReactAppServerAsync( - string sourcePath, string npmScriptName, ILogger logger) + string sourcePath, string npmScriptName, string pkgManagerName, ILogger logger) { var portNumber = TcpPortFinder.FindAvailablePort(); logger.LogInformation($"Starting create-react-app server on port {portNumber}..."); @@ -71,8 +72,8 @@ namespace Microsoft.AspNetCore.SpaServices.ReactDevelopmentServer { "PORT", portNumber.ToString() }, { "BROWSER", "none" }, // We don't want create-react-app to open its own extra browser window pointing to the internal dev server port }; - var npmScriptRunner = new NpmScriptRunner( - sourcePath, npmScriptName, null, envVars); + var npmScriptRunner = new NodeScriptRunner( + sourcePath, npmScriptName, null, envVars, pkgManagerName); npmScriptRunner.AttachToLogger(logger); using (var stdErrReader = new EventedStreamStringReader(npmScriptRunner.StdErr)) diff --git a/src/Middleware/SpaServices.Extensions/src/ReactDevelopmentServer/ReactDevelopmentServerMiddlewareExtensions.cs b/src/Middleware/SpaServices.Extensions/src/ReactDevelopmentServer/ReactDevelopmentServerMiddlewareExtensions.cs index f58a6d1a9d..346e839046 100644 --- a/src/Middleware/SpaServices.Extensions/src/ReactDevelopmentServer/ReactDevelopmentServerMiddlewareExtensions.cs +++ b/src/Middleware/SpaServices.Extensions/src/ReactDevelopmentServer/ReactDevelopmentServerMiddlewareExtensions.cs @@ -1,4 +1,4 @@ -// Copyright (c) .NET Foundation. All rights reserved. +// 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 Microsoft.AspNetCore.Builder; diff --git a/src/Middleware/SpaServices.Extensions/src/SpaOptions.cs b/src/Middleware/SpaServices.Extensions/src/SpaOptions.cs index b2823396dc..5b3d06a94d 100644 --- a/src/Middleware/SpaServices.Extensions/src/SpaOptions.cs +++ b/src/Middleware/SpaServices.Extensions/src/SpaOptions.cs @@ -1,11 +1,10 @@ -// Copyright (c) .NET Foundation. All rights reserved. +// 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 Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Http; -using Microsoft.Extensions.FileProviders; -using System; namespace Microsoft.AspNetCore.SpaServices { @@ -15,6 +14,7 @@ namespace Microsoft.AspNetCore.SpaServices public class SpaOptions { private PathString _defaultPage = "/index.html"; + private string _defaultPackageManagerName = "npm"; /// /// Constructs a new instance of . @@ -30,6 +30,7 @@ namespace Microsoft.AspNetCore.SpaServices internal SpaOptions(SpaOptions copyFromOptions) { _defaultPage = copyFromOptions.DefaultPage; + _defaultPackageManagerName = copyFromOptions.PackageManagerName; DefaultPageStaticFileOptions = copyFromOptions.DefaultPageStaticFileOptions; SourcePath = copyFromOptions.SourcePath; } @@ -69,6 +70,27 @@ namespace Microsoft.AspNetCore.SpaServices /// public string SourcePath { get; set; } + /// + /// Gets or sets the name of the package manager executible, (e.g npm, + /// yarn) to run the SPA. + /// + /// If not set, npm will be assumed as the default package manager + /// executable + /// + public string PackageManagerName + { + get => _defaultPackageManagerName; + set + { + if (string.IsNullOrEmpty(value)) + { + throw new ArgumentException($"The value for {nameof(PackageManagerName)} cannot be null or empty."); + } + + _defaultPackageManagerName = value; + } + } + /// /// Gets or sets the maximum duration that a request will wait for the SPA /// to become ready to serve to the client. From 7c7dd6569f8eabc3586d9a0aed296e46c63fcc98 Mon Sep 17 00:00:00 2001 From: Justin Robb Date: Wed, 31 Jul 2019 13:15:07 -0700 Subject: [PATCH 0133/1101] Renaming some SPA Middleware variables to be package manager agnostic --- .../src/AngularCli/AngularCliBuilder.cs | 29 +++++++++---------- .../src/AngularCli/AngularCliMiddleware.cs | 22 +++++++------- .../AngularCliMiddlewareExtensions.cs | 8 ++--- .../ReactDevelopmentServerMiddleware.cs | 22 +++++++------- ...ctDevelopmentServerMiddlewareExtensions.cs | 6 ++-- 5 files changed, 43 insertions(+), 44 deletions(-) diff --git a/src/Middleware/SpaServices.Extensions/src/AngularCli/AngularCliBuilder.cs b/src/Middleware/SpaServices.Extensions/src/AngularCli/AngularCliBuilder.cs index 0a6a320193..b6cc1f18c6 100644 --- a/src/Middleware/SpaServices.Extensions/src/AngularCli/AngularCliBuilder.cs +++ b/src/Middleware/SpaServices.Extensions/src/AngularCli/AngularCliBuilder.cs @@ -21,20 +21,20 @@ namespace Microsoft.AspNetCore.SpaServices.AngularCli { private static TimeSpan RegexMatchTimeout = TimeSpan.FromSeconds(5); // This is a development-time only feature, so a very long timeout is fine - private readonly string _npmScriptName; + private readonly string _scriptName; /// /// Constructs an instance of . /// - /// The name of the script in your package.json file that builds the server-side bundle for your Angular application. - public AngularCliBuilder(string npmScript) + /// The name of the script in your package.json file that builds the server-side bundle for your Angular application. + public AngularCliBuilder(string scriptName) { - if (string.IsNullOrEmpty(npmScript)) + if (string.IsNullOrEmpty(scriptName)) { - throw new ArgumentException("Cannot be null or empty.", nameof(npmScript)); + throw new ArgumentException("Cannot be null or empty.", nameof(scriptName)); } - _npmScriptName = npmScript; + _scriptName = scriptName; } /// @@ -47,37 +47,36 @@ namespace Microsoft.AspNetCore.SpaServices.AngularCli throw new InvalidOperationException($"To use {nameof(AngularCliBuilder)}, you must supply a non-empty value for the {nameof(SpaOptions.SourcePath)} property of {nameof(SpaOptions)} when calling {nameof(SpaApplicationBuilderExtensions.UseSpa)}."); } - var logger = LoggerFinder.GetOrCreateLogger( spaBuilder.ApplicationBuilder, nameof(AngularCliBuilder)); - var npmScriptRunner = new NodeScriptRunner( + var scriptRunner = new NodeScriptRunner( sourcePath, - _npmScriptName, + _scriptName, "--watch", null, pkgManagerName); - npmScriptRunner.AttachToLogger(logger); + scriptRunner.AttachToLogger(logger); - using (var stdOutReader = new EventedStreamStringReader(npmScriptRunner.StdOut)) - using (var stdErrReader = new EventedStreamStringReader(npmScriptRunner.StdErr)) + using (var stdOutReader = new EventedStreamStringReader(scriptRunner.StdOut)) + using (var stdErrReader = new EventedStreamStringReader(scriptRunner.StdErr)) { try { - await npmScriptRunner.StdOut.WaitForMatch( + await scriptRunner.StdOut.WaitForMatch( new Regex("Date", RegexOptions.None, RegexMatchTimeout)); } catch (EndOfStreamException ex) { throw new InvalidOperationException( - $"The NPM script '{_npmScriptName}' exited without indicating success.\n" + + $"The {pkgManagerName} script '{_scriptName}' exited without indicating success.\n" + $"Output was: {stdOutReader.ReadAsString()}\n" + $"Error output was: {stdErrReader.ReadAsString()}", ex); } catch (OperationCanceledException ex) { throw new InvalidOperationException( - $"The NPM script '{_npmScriptName}' timed out without indicating success. " + + $"The {pkgManagerName} script '{_scriptName}' timed out without indicating success. " + $"Output was: {stdOutReader.ReadAsString()}\n" + $"Error output was: {stdErrReader.ReadAsString()}", ex); } diff --git a/src/Middleware/SpaServices.Extensions/src/AngularCli/AngularCliMiddleware.cs b/src/Middleware/SpaServices.Extensions/src/AngularCli/AngularCliMiddleware.cs index 1f1ba8cdc1..dfddd1800a 100644 --- a/src/Middleware/SpaServices.Extensions/src/AngularCli/AngularCliMiddleware.cs +++ b/src/Middleware/SpaServices.Extensions/src/AngularCli/AngularCliMiddleware.cs @@ -23,7 +23,7 @@ namespace Microsoft.AspNetCore.SpaServices.AngularCli public static void Attach( ISpaBuilder spaBuilder, - string npmScriptName) + string scriptName) { var pkgManagerName = spaBuilder.Options.PackageManagerName; var sourcePath = spaBuilder.Options.SourcePath; @@ -32,15 +32,15 @@ namespace Microsoft.AspNetCore.SpaServices.AngularCli throw new ArgumentException("Cannot be null or empty", nameof(sourcePath)); } - if (string.IsNullOrEmpty(npmScriptName)) + if (string.IsNullOrEmpty(scriptName)) { - throw new ArgumentException("Cannot be null or empty", nameof(npmScriptName)); + throw new ArgumentException("Cannot be null or empty", nameof(scriptName)); } // Start Angular CLI and attach to middleware pipeline var appBuilder = spaBuilder.ApplicationBuilder; var logger = LoggerFinder.GetOrCreateLogger(appBuilder, LogCategoryName); - var angularCliServerInfoTask = StartAngularCliServerAsync(sourcePath, npmScriptName, pkgManagerName, logger); + var angularCliServerInfoTask = StartAngularCliServerAsync(sourcePath, scriptName, pkgManagerName, logger); // Everything we proxy is hardcoded to target http://localhost because: // - the requests are always from the local machine (we're not accepting remote @@ -63,27 +63,27 @@ namespace Microsoft.AspNetCore.SpaServices.AngularCli } private static async Task StartAngularCliServerAsync( - string sourcePath, string npmScriptName, string pkgManagerName, ILogger logger) + string sourcePath, string scriptName, string pkgManagerName, ILogger logger) { var portNumber = TcpPortFinder.FindAvailablePort(); logger.LogInformation($"Starting @angular/cli on port {portNumber}..."); - var npmScriptRunner = new NodeScriptRunner( - sourcePath, npmScriptName, $"--port {portNumber}", null, pkgManagerName); - npmScriptRunner.AttachToLogger(logger); + var scriptRunner = new NodeScriptRunner( + sourcePath, scriptName, $"--port {portNumber}", null, pkgManagerName); + scriptRunner.AttachToLogger(logger); Match openBrowserLine; - using (var stdErrReader = new EventedStreamStringReader(npmScriptRunner.StdErr)) + using (var stdErrReader = new EventedStreamStringReader(scriptRunner.StdErr)) { try { - openBrowserLine = await npmScriptRunner.StdOut.WaitForMatch( + openBrowserLine = await scriptRunner.StdOut.WaitForMatch( new Regex("open your browser on (http\\S+)", RegexOptions.None, RegexMatchTimeout)); } catch (EndOfStreamException ex) { throw new InvalidOperationException( - $"The NPM script '{npmScriptName}' exited without indicating that the " + + $"The {pkgManagerName} script '{scriptName}' exited without indicating that the " + $"Angular CLI was listening for requests. The error output was: " + $"{stdErrReader.ReadAsString()}", ex); } diff --git a/src/Middleware/SpaServices.Extensions/src/AngularCli/AngularCliMiddlewareExtensions.cs b/src/Middleware/SpaServices.Extensions/src/AngularCli/AngularCliMiddlewareExtensions.cs index 28e63c8e35..59f8ee5046 100644 --- a/src/Middleware/SpaServices.Extensions/src/AngularCli/AngularCliMiddlewareExtensions.cs +++ b/src/Middleware/SpaServices.Extensions/src/AngularCli/AngularCliMiddlewareExtensions.cs @@ -1,4 +1,4 @@ -// Copyright (c) .NET Foundation. All rights reserved. +// 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 Microsoft.AspNetCore.Builder; @@ -20,10 +20,10 @@ namespace Microsoft.AspNetCore.SpaServices.AngularCli /// sure not to enable the Angular CLI server. /// /// The . - /// The name of the script in your package.json file that launches the Angular CLI process. + /// The name of the script in your package.json file that launches the Angular CLI process. public static void UseAngularCliServer( this ISpaBuilder spaBuilder, - string npmScript) + string scriptName) { if (spaBuilder == null) { @@ -37,7 +37,7 @@ namespace Microsoft.AspNetCore.SpaServices.AngularCli throw new InvalidOperationException($"To use {nameof(UseAngularCliServer)}, you must supply a non-empty value for the {nameof(SpaOptions.SourcePath)} property of {nameof(SpaOptions)} when calling {nameof(SpaApplicationBuilderExtensions.UseSpa)}."); } - AngularCliMiddleware.Attach(spaBuilder, npmScript); + AngularCliMiddleware.Attach(spaBuilder, scriptName); } } } diff --git a/src/Middleware/SpaServices.Extensions/src/ReactDevelopmentServer/ReactDevelopmentServerMiddleware.cs b/src/Middleware/SpaServices.Extensions/src/ReactDevelopmentServer/ReactDevelopmentServerMiddleware.cs index 4dfe28457d..e7ab1b3dea 100644 --- a/src/Middleware/SpaServices.Extensions/src/ReactDevelopmentServer/ReactDevelopmentServerMiddleware.cs +++ b/src/Middleware/SpaServices.Extensions/src/ReactDevelopmentServer/ReactDevelopmentServerMiddleware.cs @@ -22,7 +22,7 @@ namespace Microsoft.AspNetCore.SpaServices.ReactDevelopmentServer public static void Attach( ISpaBuilder spaBuilder, - string npmScriptName) + string scriptName) { var pkgManagerName = spaBuilder.Options.PackageManagerName; var sourcePath = spaBuilder.Options.SourcePath; @@ -31,15 +31,15 @@ namespace Microsoft.AspNetCore.SpaServices.ReactDevelopmentServer throw new ArgumentException("Cannot be null or empty", nameof(sourcePath)); } - if (string.IsNullOrEmpty(npmScriptName)) + if (string.IsNullOrEmpty(scriptName)) { - throw new ArgumentException("Cannot be null or empty", nameof(npmScriptName)); + throw new ArgumentException("Cannot be null or empty", nameof(scriptName)); } // Start create-react-app and attach to middleware pipeline var appBuilder = spaBuilder.ApplicationBuilder; var logger = LoggerFinder.GetOrCreateLogger(appBuilder, LogCategoryName); - var portTask = StartCreateReactAppServerAsync(sourcePath, npmScriptName, pkgManagerName, logger); + var portTask = StartCreateReactAppServerAsync(sourcePath, scriptName, pkgManagerName, logger); // Everything we proxy is hardcoded to target http://localhost because: // - the requests are always from the local machine (we're not accepting remote @@ -62,7 +62,7 @@ namespace Microsoft.AspNetCore.SpaServices.ReactDevelopmentServer } private static async Task StartCreateReactAppServerAsync( - string sourcePath, string npmScriptName, string pkgManagerName, ILogger logger) + string sourcePath, string scriptName, string pkgManagerName, ILogger logger) { var portNumber = TcpPortFinder.FindAvailablePort(); logger.LogInformation($"Starting create-react-app server on port {portNumber}..."); @@ -72,11 +72,11 @@ namespace Microsoft.AspNetCore.SpaServices.ReactDevelopmentServer { "PORT", portNumber.ToString() }, { "BROWSER", "none" }, // We don't want create-react-app to open its own extra browser window pointing to the internal dev server port }; - var npmScriptRunner = new NodeScriptRunner( - sourcePath, npmScriptName, null, envVars, pkgManagerName); - npmScriptRunner.AttachToLogger(logger); + var scriptRunner = new NodeScriptRunner( + sourcePath, scriptName, null, envVars, pkgManagerName); + scriptRunner.AttachToLogger(logger); - using (var stdErrReader = new EventedStreamStringReader(npmScriptRunner.StdErr)) + using (var stdErrReader = new EventedStreamStringReader(scriptRunner.StdErr)) { try { @@ -84,13 +84,13 @@ namespace Microsoft.AspNetCore.SpaServices.ReactDevelopmentServer // it doesn't do so until it's finished compiling, and even then only if there were // no compiler warnings. So instead of waiting for that, consider it ready as soon // as it starts listening for requests. - await npmScriptRunner.StdOut.WaitForMatch( + await scriptRunner.StdOut.WaitForMatch( new Regex("Starting the development server", RegexOptions.None, RegexMatchTimeout)); } catch (EndOfStreamException ex) { throw new InvalidOperationException( - $"The NPM script '{npmScriptName}' exited without indicating that the " + + $"The {pkgManagerName} script '{scriptName}' exited without indicating that the " + $"create-react-app server was listening for requests. The error output was: " + $"{stdErrReader.ReadAsString()}", ex); } diff --git a/src/Middleware/SpaServices.Extensions/src/ReactDevelopmentServer/ReactDevelopmentServerMiddlewareExtensions.cs b/src/Middleware/SpaServices.Extensions/src/ReactDevelopmentServer/ReactDevelopmentServerMiddlewareExtensions.cs index 346e839046..37532329ee 100644 --- a/src/Middleware/SpaServices.Extensions/src/ReactDevelopmentServer/ReactDevelopmentServerMiddlewareExtensions.cs +++ b/src/Middleware/SpaServices.Extensions/src/ReactDevelopmentServer/ReactDevelopmentServerMiddlewareExtensions.cs @@ -20,10 +20,10 @@ namespace Microsoft.AspNetCore.SpaServices.ReactDevelopmentServer /// sure not to enable the create-react-app server. /// /// The . - /// The name of the script in your package.json file that launches the create-react-app server. + /// The name of the script in your package.json file that launches the create-react-app server. public static void UseReactDevelopmentServer( this ISpaBuilder spaBuilder, - string npmScript) + string scriptName) { if (spaBuilder == null) { @@ -37,7 +37,7 @@ namespace Microsoft.AspNetCore.SpaServices.ReactDevelopmentServer throw new InvalidOperationException($"To use {nameof(UseReactDevelopmentServer)}, you must supply a non-empty value for the {nameof(SpaOptions.SourcePath)} property of {nameof(SpaOptions)} when calling {nameof(SpaApplicationBuilderExtensions.UseSpa)}."); } - ReactDevelopmentServerMiddleware.Attach(spaBuilder, npmScript); + ReactDevelopmentServerMiddleware.Attach(spaBuilder, scriptName); } } } From c3983614121adb3ad7133c18a129048ddc5acce4 Mon Sep 17 00:00:00 2001 From: Justin Robb Date: Thu, 1 Aug 2019 10:14:51 -0700 Subject: [PATCH 0134/1101] Renaming packageManagerCommand variables, fixing comment --- .../src/AngularCli/AngularCliBuilder.cs | 8 ++++---- .../src/AngularCli/AngularCliMiddleware.cs | 10 +++++----- .../src/Npm/NodeScriptRunner.cs | 12 ++++++------ .../ReactDevelopmentServerMiddleware.cs | 10 +++++----- .../SpaServices.Extensions/src/SpaOptions.cs | 15 +++++++-------- 5 files changed, 27 insertions(+), 28 deletions(-) diff --git a/src/Middleware/SpaServices.Extensions/src/AngularCli/AngularCliBuilder.cs b/src/Middleware/SpaServices.Extensions/src/AngularCli/AngularCliBuilder.cs index b6cc1f18c6..df19fa76d9 100644 --- a/src/Middleware/SpaServices.Extensions/src/AngularCli/AngularCliBuilder.cs +++ b/src/Middleware/SpaServices.Extensions/src/AngularCli/AngularCliBuilder.cs @@ -40,7 +40,7 @@ namespace Microsoft.AspNetCore.SpaServices.AngularCli /// public async Task Build(ISpaBuilder spaBuilder) { - var pkgManagerName = spaBuilder.Options.PackageManagerName; + var pkgManagerCommand = spaBuilder.Options.PackageManagerCommand; var sourcePath = spaBuilder.Options.SourcePath; if (string.IsNullOrEmpty(sourcePath)) { @@ -55,7 +55,7 @@ namespace Microsoft.AspNetCore.SpaServices.AngularCli _scriptName, "--watch", null, - pkgManagerName); + pkgManagerCommand); scriptRunner.AttachToLogger(logger); using (var stdOutReader = new EventedStreamStringReader(scriptRunner.StdOut)) @@ -69,14 +69,14 @@ namespace Microsoft.AspNetCore.SpaServices.AngularCli catch (EndOfStreamException ex) { throw new InvalidOperationException( - $"The {pkgManagerName} script '{_scriptName}' exited without indicating success.\n" + + $"The {pkgManagerCommand} script '{_scriptName}' exited without indicating success.\n" + $"Output was: {stdOutReader.ReadAsString()}\n" + $"Error output was: {stdErrReader.ReadAsString()}", ex); } catch (OperationCanceledException ex) { throw new InvalidOperationException( - $"The {pkgManagerName} script '{_scriptName}' timed out without indicating success. " + + $"The {pkgManagerCommand} script '{_scriptName}' timed out without indicating success. " + $"Output was: {stdOutReader.ReadAsString()}\n" + $"Error output was: {stdErrReader.ReadAsString()}", ex); } diff --git a/src/Middleware/SpaServices.Extensions/src/AngularCli/AngularCliMiddleware.cs b/src/Middleware/SpaServices.Extensions/src/AngularCli/AngularCliMiddleware.cs index dfddd1800a..c4e109b8f7 100644 --- a/src/Middleware/SpaServices.Extensions/src/AngularCli/AngularCliMiddleware.cs +++ b/src/Middleware/SpaServices.Extensions/src/AngularCli/AngularCliMiddleware.cs @@ -25,7 +25,7 @@ namespace Microsoft.AspNetCore.SpaServices.AngularCli ISpaBuilder spaBuilder, string scriptName) { - var pkgManagerName = spaBuilder.Options.PackageManagerName; + var pkgManagerCommand = spaBuilder.Options.PackageManagerCommand; var sourcePath = spaBuilder.Options.SourcePath; if (string.IsNullOrEmpty(sourcePath)) { @@ -40,7 +40,7 @@ namespace Microsoft.AspNetCore.SpaServices.AngularCli // Start Angular CLI and attach to middleware pipeline var appBuilder = spaBuilder.ApplicationBuilder; var logger = LoggerFinder.GetOrCreateLogger(appBuilder, LogCategoryName); - var angularCliServerInfoTask = StartAngularCliServerAsync(sourcePath, scriptName, pkgManagerName, logger); + var angularCliServerInfoTask = StartAngularCliServerAsync(sourcePath, scriptName, pkgManagerCommand, logger); // Everything we proxy is hardcoded to target http://localhost because: // - the requests are always from the local machine (we're not accepting remote @@ -63,13 +63,13 @@ namespace Microsoft.AspNetCore.SpaServices.AngularCli } private static async Task StartAngularCliServerAsync( - string sourcePath, string scriptName, string pkgManagerName, ILogger logger) + string sourcePath, string scriptName, string pkgManagerCommand, ILogger logger) { var portNumber = TcpPortFinder.FindAvailablePort(); logger.LogInformation($"Starting @angular/cli on port {portNumber}..."); var scriptRunner = new NodeScriptRunner( - sourcePath, scriptName, $"--port {portNumber}", null, pkgManagerName); + sourcePath, scriptName, $"--port {portNumber}", null, pkgManagerCommand); scriptRunner.AttachToLogger(logger); Match openBrowserLine; @@ -83,7 +83,7 @@ namespace Microsoft.AspNetCore.SpaServices.AngularCli catch (EndOfStreamException ex) { throw new InvalidOperationException( - $"The {pkgManagerName} script '{scriptName}' exited without indicating that the " + + $"The {pkgManagerCommand} script '{scriptName}' exited without indicating that the " + $"Angular CLI was listening for requests. The error output was: " + $"{stdErrReader.ReadAsString()}", ex); } diff --git a/src/Middleware/SpaServices.Extensions/src/Npm/NodeScriptRunner.cs b/src/Middleware/SpaServices.Extensions/src/Npm/NodeScriptRunner.cs index 86e6497f19..f08abeb19c 100644 --- a/src/Middleware/SpaServices.Extensions/src/Npm/NodeScriptRunner.cs +++ b/src/Middleware/SpaServices.Extensions/src/Npm/NodeScriptRunner.cs @@ -23,7 +23,7 @@ namespace Microsoft.AspNetCore.NodeServices.Npm private static Regex AnsiColorRegex = new Regex("\x001b\\[[0-9;]*m", RegexOptions.None, TimeSpan.FromSeconds(1)); - public NodeScriptRunner(string workingDirectory, string scriptName, string arguments, IDictionary envVars, string pkgManagerName) + public NodeScriptRunner(string workingDirectory, string scriptName, string arguments, IDictionary envVars, string pkgManagerCommand) { if (string.IsNullOrEmpty(workingDirectory)) { @@ -35,12 +35,12 @@ namespace Microsoft.AspNetCore.NodeServices.Npm throw new ArgumentException("Cannot be null or empty.", nameof(scriptName)); } - if (string.IsNullOrEmpty(pkgManagerName)) + if (string.IsNullOrEmpty(pkgManagerCommand)) { - throw new ArgumentException("Cannot be null or empty.", nameof(pkgManagerName)); + throw new ArgumentException("Cannot be null or empty.", nameof(pkgManagerCommand)); } - var exeToRun = pkgManagerName; + var exeToRun = pkgManagerCommand; var completeArguments = $"run {scriptName} -- {arguments ?? string.Empty}"; if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) { @@ -48,7 +48,7 @@ namespace Microsoft.AspNetCore.NodeServices.Npm // directly (except with UseShellExecute=true, but that's no good, because // it prevents capturing stdio). So we need to invoke it via "cmd /c". exeToRun = "cmd"; - completeArguments = $"/c {pkgManagerName} {completeArguments}"; + completeArguments = $"/c {pkgManagerCommand} {completeArguments}"; } var processStartInfo = new ProcessStartInfo(exeToRun) @@ -69,7 +69,7 @@ namespace Microsoft.AspNetCore.NodeServices.Npm } } - var process = LaunchNodeProcess(processStartInfo, pkgManagerName); + var process = LaunchNodeProcess(processStartInfo, pkgManagerCommand); StdOut = new EventedStreamReader(process.StandardOutput); StdErr = new EventedStreamReader(process.StandardError); } diff --git a/src/Middleware/SpaServices.Extensions/src/ReactDevelopmentServer/ReactDevelopmentServerMiddleware.cs b/src/Middleware/SpaServices.Extensions/src/ReactDevelopmentServer/ReactDevelopmentServerMiddleware.cs index e7ab1b3dea..6566fef706 100644 --- a/src/Middleware/SpaServices.Extensions/src/ReactDevelopmentServer/ReactDevelopmentServerMiddleware.cs +++ b/src/Middleware/SpaServices.Extensions/src/ReactDevelopmentServer/ReactDevelopmentServerMiddleware.cs @@ -24,7 +24,7 @@ namespace Microsoft.AspNetCore.SpaServices.ReactDevelopmentServer ISpaBuilder spaBuilder, string scriptName) { - var pkgManagerName = spaBuilder.Options.PackageManagerName; + var pkgManagerCommand = spaBuilder.Options.PackageManagerCommand; var sourcePath = spaBuilder.Options.SourcePath; if (string.IsNullOrEmpty(sourcePath)) { @@ -39,7 +39,7 @@ namespace Microsoft.AspNetCore.SpaServices.ReactDevelopmentServer // Start create-react-app and attach to middleware pipeline var appBuilder = spaBuilder.ApplicationBuilder; var logger = LoggerFinder.GetOrCreateLogger(appBuilder, LogCategoryName); - var portTask = StartCreateReactAppServerAsync(sourcePath, scriptName, pkgManagerName, logger); + var portTask = StartCreateReactAppServerAsync(sourcePath, scriptName, pkgManagerCommand, logger); // Everything we proxy is hardcoded to target http://localhost because: // - the requests are always from the local machine (we're not accepting remote @@ -62,7 +62,7 @@ namespace Microsoft.AspNetCore.SpaServices.ReactDevelopmentServer } private static async Task StartCreateReactAppServerAsync( - string sourcePath, string scriptName, string pkgManagerName, ILogger logger) + string sourcePath, string scriptName, string pkgManagerCommand, ILogger logger) { var portNumber = TcpPortFinder.FindAvailablePort(); logger.LogInformation($"Starting create-react-app server on port {portNumber}..."); @@ -73,7 +73,7 @@ namespace Microsoft.AspNetCore.SpaServices.ReactDevelopmentServer { "BROWSER", "none" }, // We don't want create-react-app to open its own extra browser window pointing to the internal dev server port }; var scriptRunner = new NodeScriptRunner( - sourcePath, scriptName, null, envVars, pkgManagerName); + sourcePath, scriptName, null, envVars, pkgManagerCommand); scriptRunner.AttachToLogger(logger); using (var stdErrReader = new EventedStreamStringReader(scriptRunner.StdErr)) @@ -90,7 +90,7 @@ namespace Microsoft.AspNetCore.SpaServices.ReactDevelopmentServer catch (EndOfStreamException ex) { throw new InvalidOperationException( - $"The {pkgManagerName} script '{scriptName}' exited without indicating that the " + + $"The {pkgManagerCommand} script '{scriptName}' exited without indicating that the " + $"create-react-app server was listening for requests. The error output was: " + $"{stdErrReader.ReadAsString()}", ex); } diff --git a/src/Middleware/SpaServices.Extensions/src/SpaOptions.cs b/src/Middleware/SpaServices.Extensions/src/SpaOptions.cs index 5b3d06a94d..59ccc1eda4 100644 --- a/src/Middleware/SpaServices.Extensions/src/SpaOptions.cs +++ b/src/Middleware/SpaServices.Extensions/src/SpaOptions.cs @@ -14,7 +14,7 @@ namespace Microsoft.AspNetCore.SpaServices public class SpaOptions { private PathString _defaultPage = "/index.html"; - private string _defaultPackageManagerName = "npm"; + private string _packageManagerCommand = "npm"; /// /// Constructs a new instance of . @@ -30,7 +30,7 @@ namespace Microsoft.AspNetCore.SpaServices internal SpaOptions(SpaOptions copyFromOptions) { _defaultPage = copyFromOptions.DefaultPage; - _defaultPackageManagerName = copyFromOptions.PackageManagerName; + _packageManagerCommand = copyFromOptions.PackageManagerCommand; DefaultPageStaticFileOptions = copyFromOptions.DefaultPageStaticFileOptions; SourcePath = copyFromOptions.SourcePath; } @@ -74,20 +74,19 @@ namespace Microsoft.AspNetCore.SpaServices /// Gets or sets the name of the package manager executible, (e.g npm, /// yarn) to run the SPA. /// - /// If not set, npm will be assumed as the default package manager - /// executable + /// The default value is 'npm'. /// - public string PackageManagerName + public string PackageManagerCommand { - get => _defaultPackageManagerName; + get => _packageManagerCommand; set { if (string.IsNullOrEmpty(value)) { - throw new ArgumentException($"The value for {nameof(PackageManagerName)} cannot be null or empty."); + throw new ArgumentException($"The value for {nameof(PackageManagerCommand)} cannot be null or empty."); } - _defaultPackageManagerName = value; + _packageManagerCommand = value; } } From 102bcf6739957d772833d20a78f751a63719a4b8 Mon Sep 17 00:00:00 2001 From: Justin Robb Date: Thu, 1 Aug 2019 12:13:37 -0700 Subject: [PATCH 0135/1101] Reverting breaking changes with npmScript parameter name --- .../src/AngularCli/AngularCliBuilder.cs | 10 +++++----- .../src/AngularCli/AngularCliMiddlewareExtensions.cs | 6 +++--- .../ReactDevelopmentServerMiddlewareExtensions.cs | 6 +++--- 3 files changed, 11 insertions(+), 11 deletions(-) diff --git a/src/Middleware/SpaServices.Extensions/src/AngularCli/AngularCliBuilder.cs b/src/Middleware/SpaServices.Extensions/src/AngularCli/AngularCliBuilder.cs index df19fa76d9..c78e194726 100644 --- a/src/Middleware/SpaServices.Extensions/src/AngularCli/AngularCliBuilder.cs +++ b/src/Middleware/SpaServices.Extensions/src/AngularCli/AngularCliBuilder.cs @@ -26,15 +26,15 @@ namespace Microsoft.AspNetCore.SpaServices.AngularCli /// /// Constructs an instance of . /// - /// The name of the script in your package.json file that builds the server-side bundle for your Angular application. - public AngularCliBuilder(string scriptName) + /// The name of the script in your package.json file that builds the server-side bundle for your Angular application. + public AngularCliBuilder(string npmScript) { - if (string.IsNullOrEmpty(scriptName)) + if (string.IsNullOrEmpty(npmScript)) { - throw new ArgumentException("Cannot be null or empty.", nameof(scriptName)); + throw new ArgumentException("Cannot be null or empty.", nameof(npmScript)); } - _scriptName = scriptName; + _scriptName = npmScript; } /// diff --git a/src/Middleware/SpaServices.Extensions/src/AngularCli/AngularCliMiddlewareExtensions.cs b/src/Middleware/SpaServices.Extensions/src/AngularCli/AngularCliMiddlewareExtensions.cs index 59f8ee5046..8f8176447b 100644 --- a/src/Middleware/SpaServices.Extensions/src/AngularCli/AngularCliMiddlewareExtensions.cs +++ b/src/Middleware/SpaServices.Extensions/src/AngularCli/AngularCliMiddlewareExtensions.cs @@ -20,10 +20,10 @@ namespace Microsoft.AspNetCore.SpaServices.AngularCli /// sure not to enable the Angular CLI server. /// /// The . - /// The name of the script in your package.json file that launches the Angular CLI process. + /// The name of the script in your package.json file that launches the Angular CLI process. public static void UseAngularCliServer( this ISpaBuilder spaBuilder, - string scriptName) + string npmScript) { if (spaBuilder == null) { @@ -37,7 +37,7 @@ namespace Microsoft.AspNetCore.SpaServices.AngularCli throw new InvalidOperationException($"To use {nameof(UseAngularCliServer)}, you must supply a non-empty value for the {nameof(SpaOptions.SourcePath)} property of {nameof(SpaOptions)} when calling {nameof(SpaApplicationBuilderExtensions.UseSpa)}."); } - AngularCliMiddleware.Attach(spaBuilder, scriptName); + AngularCliMiddleware.Attach(spaBuilder, npmScript); } } } diff --git a/src/Middleware/SpaServices.Extensions/src/ReactDevelopmentServer/ReactDevelopmentServerMiddlewareExtensions.cs b/src/Middleware/SpaServices.Extensions/src/ReactDevelopmentServer/ReactDevelopmentServerMiddlewareExtensions.cs index 37532329ee..346e839046 100644 --- a/src/Middleware/SpaServices.Extensions/src/ReactDevelopmentServer/ReactDevelopmentServerMiddlewareExtensions.cs +++ b/src/Middleware/SpaServices.Extensions/src/ReactDevelopmentServer/ReactDevelopmentServerMiddlewareExtensions.cs @@ -20,10 +20,10 @@ namespace Microsoft.AspNetCore.SpaServices.ReactDevelopmentServer /// sure not to enable the create-react-app server. /// /// The . - /// The name of the script in your package.json file that launches the create-react-app server. + /// The name of the script in your package.json file that launches the create-react-app server. public static void UseReactDevelopmentServer( this ISpaBuilder spaBuilder, - string scriptName) + string npmScript) { if (spaBuilder == null) { @@ -37,7 +37,7 @@ namespace Microsoft.AspNetCore.SpaServices.ReactDevelopmentServer throw new InvalidOperationException($"To use {nameof(UseReactDevelopmentServer)}, you must supply a non-empty value for the {nameof(SpaOptions.SourcePath)} property of {nameof(SpaOptions)} when calling {nameof(SpaApplicationBuilderExtensions.UseSpa)}."); } - ReactDevelopmentServerMiddleware.Attach(spaBuilder, scriptName); + ReactDevelopmentServerMiddleware.Attach(spaBuilder, npmScript); } } } From 9adf27409eb742282692764ef4eaaf7018fc022b Mon Sep 17 00:00:00 2001 From: Justin Robb Date: Thu, 1 Aug 2019 13:46:56 -0700 Subject: [PATCH 0136/1101] Regenerating reference assemblies for Microsoft.AspNetCore.SpaServices.Extensions --- .../Microsoft.AspNetCore.SpaServices.Extensions.netcoreapp3.0.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Middleware/SpaServices.Extensions/ref/Microsoft.AspNetCore.SpaServices.Extensions.netcoreapp3.0.cs b/src/Middleware/SpaServices.Extensions/ref/Microsoft.AspNetCore.SpaServices.Extensions.netcoreapp3.0.cs index be28dc70e2..9dfedd1041 100644 --- a/src/Middleware/SpaServices.Extensions/ref/Microsoft.AspNetCore.SpaServices.Extensions.netcoreapp3.0.cs +++ b/src/Middleware/SpaServices.Extensions/ref/Microsoft.AspNetCore.SpaServices.Extensions.netcoreapp3.0.cs @@ -38,6 +38,7 @@ namespace Microsoft.AspNetCore.SpaServices public SpaOptions() { } public Microsoft.AspNetCore.Http.PathString DefaultPage { get { throw null; } set { } } public Microsoft.AspNetCore.Builder.StaticFileOptions DefaultPageStaticFileOptions { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } + public string PackageManagerCommand { get { throw null; } set { } } public string SourcePath { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } public System.TimeSpan StartupTimeout { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } } From 698a343128142e39ad02e6e389c554872df97678 Mon Sep 17 00:00:00 2001 From: Paul Buonopane Date: Fri, 2 Aug 2019 15:21:35 -0400 Subject: [PATCH 0137/1101] Fix #12834: Wrong order of params to ArgumentNullException ctor (#12839) Fixes #12834 --- src/Mvc/Mvc.Core/src/Routing/UrlHelperFactory.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Mvc/Mvc.Core/src/Routing/UrlHelperFactory.cs b/src/Mvc/Mvc.Core/src/Routing/UrlHelperFactory.cs index dfe16c2421..5fffa77974 100644 --- a/src/Mvc/Mvc.Core/src/Routing/UrlHelperFactory.cs +++ b/src/Mvc/Mvc.Core/src/Routing/UrlHelperFactory.cs @@ -21,7 +21,7 @@ namespace Microsoft.AspNetCore.Mvc.Routing { if (context == null) { - throw new ArgumentNullException(Resources.ArgumentCannotBeNullOrEmpty, (nameof(context))); + throw new ArgumentNullException(nameof(context)); } var httpContext = context.HttpContext; @@ -69,4 +69,4 @@ namespace Microsoft.AspNetCore.Mvc.Routing return urlHelper; } } -} \ No newline at end of file +} From 4921dc3aa3496eb3e9fe73255213ce54a0ba29d6 Mon Sep 17 00:00:00 2001 From: "dotnet-maestro[bot]" <42748379+dotnet-maestro[bot]@users.noreply.github.com> Date: Sun, 4 Aug 2019 21:24:38 +0000 Subject: [PATCH 0138/1101] [master] Update dependencies from aspnet/AspNetCore-Tooling aspnet/Blazor (#12856) * Update dependencies from https://github.com/aspnet/AspNetCore-Tooling build 20190731.2 - Microsoft.NET.Sdk.Razor - 5.0.0-alpha1.19381.2 - Microsoft.CodeAnalysis.Razor - 5.0.0-alpha1.19381.2 - Microsoft.AspNetCore.Razor.Language - 5.0.0-alpha1.19381.2 - Microsoft.AspNetCore.Mvc.Razor.Extensions - 5.0.0-alpha1.19381.2 * Update dependencies from https://github.com/aspnet/Blazor build 20190803.2 - Microsoft.AspNetCore.Blazor.Mono - 5.0.0-alpha1.19403.2 --- eng/Version.Details.xml | 20 ++++++++++---------- eng/Versions.props | 10 +++++----- 2 files changed, 15 insertions(+), 15 deletions(-) diff --git a/eng/Version.Details.xml b/eng/Version.Details.xml index dc483752d9..1e7f2fa1cf 100644 --- a/eng/Version.Details.xml +++ b/eng/Version.Details.xml @@ -9,25 +9,25 @@ --> - + https://github.com/aspnet/Blazor - cd9254a05cfa52defeacd31697d25b2a4ed17510 + dd092c2236cf9375b50e19295dd2faf36e6221f6 - + https://github.com/aspnet/AspNetCore-Tooling - f039aa935462163dead64ca2d6f9c6d27f4e290b + 65994cb6ffd2d8da87db74e2b3e34cb5e350aff0 - + https://github.com/aspnet/AspNetCore-Tooling - f039aa935462163dead64ca2d6f9c6d27f4e290b + 65994cb6ffd2d8da87db74e2b3e34cb5e350aff0 - + https://github.com/aspnet/AspNetCore-Tooling - f039aa935462163dead64ca2d6f9c6d27f4e290b + 65994cb6ffd2d8da87db74e2b3e34cb5e350aff0 - + https://github.com/aspnet/AspNetCore-Tooling - f039aa935462163dead64ca2d6f9c6d27f4e290b + 65994cb6ffd2d8da87db74e2b3e34cb5e350aff0 https://github.com/aspnet/EntityFrameworkCore diff --git a/eng/Versions.props b/eng/Versions.props index 8e33360df5..4f48ab6d95 100644 --- a/eng/Versions.props +++ b/eng/Versions.props @@ -90,7 +90,7 @@ 3.0.0-preview8.19378.8 - 3.0.0-preview9.19379.2 + 5.0.0-alpha1.19403.2 3.0.0-preview9.19401.2 3.0.0-preview9.19401.2 @@ -162,10 +162,10 @@ 3.0.0-preview9.19402.9 3.0.0-preview9.19402.9 - 3.0.0-preview9.19402.1 - 3.0.0-preview9.19402.1 - 3.0.0-preview9.19402.1 - 3.0.0-preview9.19402.1 + 5.0.0-alpha1.19381.2 + 5.0.0-alpha1.19381.2 + 5.0.0-alpha1.19381.2 + 5.0.0-alpha1.19381.2 - + https://github.com/aspnet/Blazor - dd092c2236cf9375b50e19295dd2faf36e6221f6 + b2c48dd8c9099f71908fac26089cbea2c76d06a1 https://github.com/aspnet/AspNetCore-Tooling diff --git a/eng/Versions.props b/eng/Versions.props index 4f48ab6d95..c2e7110155 100644 --- a/eng/Versions.props +++ b/eng/Versions.props @@ -90,7 +90,7 @@ 3.0.0-preview8.19378.8 - 5.0.0-alpha1.19403.2 + 5.0.0-alpha1.19405.2 3.0.0-preview9.19401.2 3.0.0-preview9.19401.2 From e7398ca25d53432eb96dc7d4e0d56be501b771a4 Mon Sep 17 00:00:00 2001 From: Beffyman Date: Tue, 6 Aug 2019 10:51:37 -0400 Subject: [PATCH 0141/1101] Blazor _LinkBlazorApplication changed to use DOTNET_HOST_PATH (#12849) * change to use host path * default to path dotnet in cases where it is using desktop msbuild --- .../Blazor/Build/src/targets/Blazor.MonoRuntime.targets | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/Components/Blazor/Build/src/targets/Blazor.MonoRuntime.targets b/src/Components/Blazor/Build/src/targets/Blazor.MonoRuntime.targets index 96a844817e..42ef903f15 100644 --- a/src/Components/Blazor/Build/src/targets/Blazor.MonoRuntime.targets +++ b/src/Components/Blazor/Build/src/targets/Blazor.MonoRuntime.targets @@ -436,9 +436,15 @@ + + + + <_MonoLinkerDotNetPath>$(DOTNET_HOST_PATH) + <_MonoLinkerDotNetPath Condition="'$(_MonoLinkerDotNetPath)' == ''">dotnet + - + From 060d35cd04db1ce06dc816a8ec0faf9fd88d7a29 Mon Sep 17 00:00:00 2001 From: Ryan Brandenburg Date: Mon, 5 Aug 2019 15:18:10 -0700 Subject: [PATCH 0142/1101] Remove ProjectTemplates ownership --- .github/CODEOWNERS | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 86a87beb59..3acc059820 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -14,7 +14,7 @@ /src/Hosting/ @tratcher @anurse /src/Http/ @tratcher @jkotalik @anurse /src/Middleware/ @tratcher @anurse -/src/ProjectTemplates/ @ryanbrandenburg +# /src/ProjectTemplates/ @ryanbrandenburg /src/Security/ @tratcher @anurse /src/Servers/ @tratcher @jkotalik @anurse @halter73 /src/Middleware/Rewrite @jkotalik @anurse From 557bd8e01116d100d7e86a541f7e0c0ebdc1e8e3 Mon Sep 17 00:00:00 2001 From: Pranav K Date: Wed, 7 Aug 2019 09:44:42 -0700 Subject: [PATCH 0143/1101] Use Utf8JsonReader in DotNetDispatcher (dotnet/extensions#2061) * Use Utf8JsonReader in DotNetDispatcher Fixes https://github.com/aspnet/AspNetCore/issues/10988 \n\nCommit migrated from https://github.com/dotnet/extensions/commit/c24711c84defc8dfbd496280c2973eab0e88cbcf --- .../src/DotNetDispatcher.cs | 163 ++++++------ .../src/JSAsyncCallResult.cs | 36 --- .../Microsoft.JSInterop/src/JSRuntimeBase.cs | 41 ++- .../test/DotNetDispatcherTest.cs | 251 +++++++++++++++++- .../test/JSRuntimeBaseTest.cs | 118 ++++++-- 5 files changed, 438 insertions(+), 171 deletions(-) delete mode 100644 src/JSInterop/Microsoft.JSInterop/src/JSAsyncCallResult.cs diff --git a/src/JSInterop/Microsoft.JSInterop/src/DotNetDispatcher.cs b/src/JSInterop/Microsoft.JSInterop/src/DotNetDispatcher.cs index 3de8f55882..0dfac228a6 100644 --- a/src/JSInterop/Microsoft.JSInterop/src/DotNetDispatcher.cs +++ b/src/JSInterop/Microsoft.JSInterop/src/DotNetDispatcher.cs @@ -7,6 +7,7 @@ using System.Collections.Generic; using System.Linq; using System.Reflection; using System.Runtime.ExceptionServices; +using System.Text; using System.Text.Json; using System.Threading.Tasks; @@ -18,7 +19,6 @@ namespace Microsoft.JSInterop public static class DotNetDispatcher { internal static readonly JsonEncodedText DotNetObjectRefKey = JsonEncodedText.Encode("__dotNetObject"); - private static readonly Type[] EndInvokeParameterTypes = new Type[] { typeof(long), typeof(bool), typeof(JSAsyncCallResult) }; private static readonly ConcurrentDictionary> _cachedMethodsByAssembly = new ConcurrentDictionary>(); @@ -74,7 +74,6 @@ namespace Microsoft.JSInterop // code has to implement its own way of returning async results. var jsRuntimeBaseInstance = (JSRuntimeBase)JSRuntime.Current; - // Using ExceptionDispatchInfo here throughout because we want to always preserve // original stack traces. object syncResult = null; @@ -165,81 +164,64 @@ namespace Microsoft.JSInterop } } - private static object[] ParseArguments(string methodIdentifier, string argsJson, Type[] parameterTypes) + internal static object[] ParseArguments(string methodIdentifier, string arguments, Type[] parameterTypes) { if (parameterTypes.Length == 0) { return Array.Empty(); } - // There's no direct way to say we want to deserialize as an array with heterogenous - // entry types (e.g., [string, int, bool]), so we need to deserialize in two phases. - var jsonDocument = JsonDocument.Parse(argsJson); - var shouldDisposeJsonDocument = true; - try + var utf8JsonBytes = Encoding.UTF8.GetBytes(arguments); + var reader = new Utf8JsonReader(utf8JsonBytes); + if (!reader.Read() || reader.TokenType != JsonTokenType.StartArray) { - if (jsonDocument.RootElement.ValueKind != JsonValueKind.Array) - { - throw new ArgumentException($"Expected a JSON array but got {jsonDocument.RootElement.ValueKind}."); - } - - var suppliedArgsLength = jsonDocument.RootElement.GetArrayLength(); - - if (suppliedArgsLength != parameterTypes.Length) - { - throw new ArgumentException($"In call to '{methodIdentifier}', expected {parameterTypes.Length} parameters but received {suppliedArgsLength}."); - } - - // Second, convert each supplied value to the type expected by the method - var suppliedArgs = new object[parameterTypes.Length]; - var index = 0; - foreach (var item in jsonDocument.RootElement.EnumerateArray()) - { - var parameterType = parameterTypes[index]; - - if (parameterType == typeof(JSAsyncCallResult)) - { - // We will pass the JsonDocument instance to JAsyncCallResult and make JSRuntimeBase - // responsible for disposing it. - shouldDisposeJsonDocument = false; - // For JS async call results, we have to defer the deserialization until - // later when we know what type it's meant to be deserialized as - suppliedArgs[index] = new JSAsyncCallResult(jsonDocument, item); - } - else if (IsIncorrectDotNetObjectRefUse(item, parameterType)) - { - throw new InvalidOperationException($"In call to '{methodIdentifier}', parameter of type '{parameterType.Name}' at index {(index + 1)} must be declared as type 'DotNetObjectRef<{parameterType.Name}>' to receive the incoming value."); - } - else - { - suppliedArgs[index] = JsonSerializer.Deserialize(item.GetRawText(), parameterType, JsonSerializerOptionsProvider.Options); - } - - index++; - } - - if (shouldDisposeJsonDocument) - { - jsonDocument.Dispose(); - } - - return suppliedArgs; - } - catch - { - // Always dispose the JsonDocument in case of an error. - jsonDocument?.Dispose(); - throw; + throw new JsonException("Invalid JSON"); } - static bool IsIncorrectDotNetObjectRefUse(JsonElement item, Type parameterType) + var suppliedArgs = new object[parameterTypes.Length]; + + var index = 0; + while (index < parameterTypes.Length && reader.Read() && reader.TokenType != JsonTokenType.EndArray) + { + var parameterType = parameterTypes[index]; + if (reader.TokenType == JsonTokenType.StartObject && IsIncorrectDotNetObjectRefUse(parameterType, reader)) + { + throw new InvalidOperationException($"In call to '{methodIdentifier}', parameter of type '{parameterType.Name}' at index {(index + 1)} must be declared as type 'DotNetObjectRef<{parameterType.Name}>' to receive the incoming value."); + } + + suppliedArgs[index] = JsonSerializer.Deserialize(ref reader, parameterType, JsonSerializerOptionsProvider.Options); + index++; + } + + if (index < parameterTypes.Length) + { + // If we parsed fewer parameters, we can always make a definitive claim about how many parameters were received. + throw new ArgumentException($"The call to '{methodIdentifier}' expects '{parameterTypes.Length}' parameters, but received '{index}'."); + } + + if (!reader.Read() || reader.TokenType != JsonTokenType.EndArray) + { + // Either we received more parameters than we expected or the JSON is malformed. + throw new JsonException($"Unexpected JSON token {reader.TokenType}. Ensure that the call to `{methodIdentifier}' is supplied with exactly '{parameterTypes.Length}' parameters."); + } + + return suppliedArgs; + + // Note that the JsonReader instance is intentionally not passed by ref (or an in parameter) since we want a copy of the original reader. + static bool IsIncorrectDotNetObjectRefUse(Type parameterType, Utf8JsonReader jsonReader) { // Check for incorrect use of DotNetObjectRef at the top level. We know it's // an incorrect use if there's a object that looks like { '__dotNetObject': }, // but we aren't assigning to DotNetObjectRef{T}. - return item.ValueKind == JsonValueKind.Object && - item.TryGetProperty(DotNetObjectRefKey.EncodedUtf8Bytes, out _) && - !typeof(IDotNetObjectRef).IsAssignableFrom(parameterType); + if (jsonReader.Read() && + jsonReader.TokenType == JsonTokenType.PropertyName && + jsonReader.ValueTextEquals(DotNetObjectRefKey.EncodedUtf8Bytes)) + { + // The JSON payload has the shape we expect from a DotNetObjectRef instance. + return !parameterType.IsGenericType || parameterType.GetGenericTypeDefinition() != typeof(DotNetObjectRef<>); + } + + return false; } } @@ -248,9 +230,9 @@ namespace Microsoft.JSInterop /// associated as completed. /// /// - /// All exceptions from are caught + /// All exceptions from are caught /// are delivered via JS interop to the JavaScript side when it requests confirmation, as - /// the mechanism to call relies on + /// the mechanism to call relies on /// using JS->.NET interop. This overload is meant for directly triggering completion callbacks /// for .NET -> JS operations without going through JS interop, so the callsite for this /// method is responsible for handling any possible exception generated from the arguments @@ -263,16 +245,40 @@ namespace Microsoft.JSInterop /// public static void EndInvoke(string arguments) { - var parsedArgs = ParseArguments( - nameof(EndInvoke), - arguments, - EndInvokeParameterTypes); - - EndInvoke((long)parsedArgs[0], (bool)parsedArgs[1], (JSAsyncCallResult)parsedArgs[2]); + var jsRuntimeBase = (JSRuntimeBase)JSRuntime.Current; + ParseEndInvokeArguments(jsRuntimeBase, arguments); } - private static void EndInvoke(long asyncHandle, bool succeeded, JSAsyncCallResult result) - => ((JSRuntimeBase)JSRuntime.Current).EndInvokeJS(asyncHandle, succeeded, result); + internal static void ParseEndInvokeArguments(JSRuntimeBase jsRuntimeBase, string arguments) + { + var utf8JsonBytes = Encoding.UTF8.GetBytes(arguments); + + // The payload that we're trying to parse is of the format + // [ taskId: long, success: boolean, value: string? | object ] + // where value is the .NET type T originally specified on InvokeAsync or the error string if success is false. + // We parse the first two arguments and call in to JSRuntimeBase to deserialize the actual value. + + var reader = new Utf8JsonReader(utf8JsonBytes); + + if (!reader.Read() || reader.TokenType != JsonTokenType.StartArray) + { + throw new JsonException("Invalid JSON"); + } + + reader.Read(); + var taskId = reader.GetInt64(); + + reader.Read(); + var success = reader.GetBoolean(); + + reader.Read(); + jsRuntimeBase.EndInvokeJS(taskId, success, ref reader); + + if (!reader.Read() || reader.TokenType != JsonTokenType.EndArray) + { + throw new JsonException("Invalid JSON"); + } + } /// /// Releases the reference to the specified .NET object. This allows the .NET runtime @@ -362,7 +368,13 @@ namespace Microsoft.JSInterop // In some edge cases this might force developers to explicitly call something on the // target assembly (from .NET) before they can invoke its allowed methods from JS. var loadedAssemblies = AppDomain.CurrentDomain.GetAssemblies(); - return loadedAssemblies.FirstOrDefault(a => new AssemblyKey(a).Equals(assemblyKey)) + + // Using LastOrDefault to workaround for https://github.com/dotnet/arcade/issues/2816. + // In most ordinary scenarios, we wouldn't have two instances of the same Assembly in the AppDomain + // so this doesn't change the outcome. + var assembly = loadedAssemblies.LastOrDefault(a => new AssemblyKey(a).Equals(assemblyKey)); + + return assembly ?? throw new ArgumentException($"There is no loaded assembly with the name '{assemblyKey.AssemblyName}'."); } @@ -396,6 +408,5 @@ namespace Microsoft.JSInterop public override int GetHashCode() => StringComparer.Ordinal.GetHashCode(AssemblyName); } - } } diff --git a/src/JSInterop/Microsoft.JSInterop/src/JSAsyncCallResult.cs b/src/JSInterop/Microsoft.JSInterop/src/JSAsyncCallResult.cs deleted file mode 100644 index 209438529b..0000000000 --- a/src/JSInterop/Microsoft.JSInterop/src/JSAsyncCallResult.cs +++ /dev/null @@ -1,36 +0,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.Text.Json; - -namespace Microsoft.JSInterop -{ - // This type takes care of a special case in handling the result of an async call from - // .NET to JS. The information about what type the result should be exists only on the - // corresponding TaskCompletionSource. We don't have that information at the time - // that we deserialize the incoming argsJson before calling DotNetDispatcher.EndInvoke. - // Declaring the EndInvoke parameter type as JSAsyncCallResult defers the deserialization - // until later when we have access to the TaskCompletionSource. - // - // There's no reason why developers would need anything similar to this in user code, - // because this is the mechanism by which we resolve the incoming argsJson to the correct - // user types before completing calls. - // - // It's marked as 'public' only because it has to be for use as an argument on a - // [JSInvokable] method. - - /// - /// Intended for framework use only. - /// - internal sealed class JSAsyncCallResult - { - internal JSAsyncCallResult(JsonDocument document, JsonElement jsonElement) - { - JsonDocument = document; - JsonElement = jsonElement; - } - - internal JsonElement JsonElement { get; } - internal JsonDocument JsonDocument { get; } - } -} diff --git a/src/JSInterop/Microsoft.JSInterop/src/JSRuntimeBase.cs b/src/JSInterop/Microsoft.JSInterop/src/JSRuntimeBase.cs index 13a9ccae1c..2121df0523 100644 --- a/src/JSInterop/Microsoft.JSInterop/src/JSRuntimeBase.cs +++ b/src/JSInterop/Microsoft.JSInterop/src/JSRuntimeBase.cs @@ -5,7 +5,6 @@ using System; using System.Collections.Concurrent; using System.Collections.Generic; using System.Linq; -using System.Runtime.ExceptionServices; using System.Text.Json; using System.Threading; using System.Threading.Tasks; @@ -139,41 +138,37 @@ namespace Microsoft.JSInterop string methodIdentifier, long dotNetObjectId); - internal void EndInvokeJS(long taskId, bool succeeded, JSAsyncCallResult asyncCallResult) + internal void EndInvokeJS(long taskId, bool succeeded, ref Utf8JsonReader jsonReader) { - using (asyncCallResult?.JsonDocument) + if (!_pendingTasks.TryRemove(taskId, out var tcs)) { - if (!_pendingTasks.TryRemove(taskId, out var tcs)) - { - // We should simply return if we can't find an id for the invocation. - // This likely means that the method that initiated the call defined a timeout and stopped waiting. - return; - } + // We should simply return if we can't find an id for the invocation. + // This likely means that the method that initiated the call defined a timeout and stopped waiting. + return; + } - CleanupTasksAndRegistrations(taskId); + CleanupTasksAndRegistrations(taskId); + try + { if (succeeded) { var resultType = TaskGenericsUtil.GetTaskCompletionSourceResultType(tcs); - try - { - var result = asyncCallResult != null ? - JsonSerializer.Deserialize(asyncCallResult.JsonElement.GetRawText(), resultType, JsonSerializerOptionsProvider.Options) : - null; - TaskGenericsUtil.SetTaskCompletionSourceResult(tcs, result); - } - catch (Exception exception) - { - var message = $"An exception occurred executing JS interop: {exception.Message}. See InnerException for more details."; - TaskGenericsUtil.SetTaskCompletionSourceException(tcs, new JSException(message, exception)); - } + + var result = JsonSerializer.Deserialize(ref jsonReader, resultType, JsonSerializerOptionsProvider.Options); + TaskGenericsUtil.SetTaskCompletionSourceResult(tcs, result); } else { - var exceptionText = asyncCallResult?.JsonElement.ToString() ?? string.Empty; + var exceptionText = jsonReader.GetString() ?? string.Empty; TaskGenericsUtil.SetTaskCompletionSourceException(tcs, new JSException(exceptionText)); } } + catch (Exception exception) + { + var message = $"An exception occurred executing JS interop: {exception.Message}. See InnerException for more details."; + TaskGenericsUtil.SetTaskCompletionSourceException(tcs, new JSException(message, exception)); + } } } } diff --git a/src/JSInterop/Microsoft.JSInterop/test/DotNetDispatcherTest.cs b/src/JSInterop/Microsoft.JSInterop/test/DotNetDispatcherTest.cs index 1c73cc63b2..8e9d0dc3b9 100644 --- a/src/JSInterop/Microsoft.JSInterop/test/DotNetDispatcherTest.cs +++ b/src/JSInterop/Microsoft.JSInterop/test/DotNetDispatcherTest.cs @@ -2,15 +2,14 @@ // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; -using System.ComponentModel.DataAnnotations; using System.Linq; using System.Runtime.ExceptionServices; using System.Text.Json; -using System.Text.Json.Serialization; +using System.Threading; using System.Threading.Tasks; using Xunit; -namespace Microsoft.JSInterop.Tests +namespace Microsoft.JSInterop { public class DotNetDispatcherTest { @@ -239,6 +238,72 @@ namespace Microsoft.JSInterop.Tests Assert.StartsWith("There is no tracked object with id '1'.", ex.Message); }); + [Fact] + public Task EndInvoke_WithSuccessValue() => WithJSRuntime(jsRuntime => + { + // Arrange + var testDTO = new TestDTO { StringVal = "Hello", IntVal = 4 }; + var task = jsRuntime.InvokeAsync("unimportant"); + var argsJson = JsonSerializer.Serialize(new object[] { jsRuntime.LastInvocationAsyncHandle, true, testDTO }, JsonSerializerOptionsProvider.Options); + + // Act + DotNetDispatcher.EndInvoke(argsJson); + + // Assert + Assert.True(task.IsCompletedSuccessfully); + var result = task.Result; + Assert.Equal(testDTO.StringVal, result.StringVal); + Assert.Equal(testDTO.IntVal, result.IntVal); + }); + + [Fact] + public Task EndInvoke_WithErrorString() => WithJSRuntime(async jsRuntime => + { + // Arrange + var expected = "Some error"; + var task = jsRuntime.InvokeAsync("unimportant"); + var argsJson = JsonSerializer.Serialize(new object[] { jsRuntime.LastInvocationAsyncHandle, false, expected }, JsonSerializerOptionsProvider.Options); + + // Act + DotNetDispatcher.EndInvoke(argsJson); + + // Assert + var ex = await Assert.ThrowsAsync(() => task); + Assert.Equal(expected, ex.Message); + }); + + [Fact(Skip = "https://github.com/aspnet/AspNetCore/issues/12357")] + public Task EndInvoke_AfterCancel() => WithJSRuntime(jsRuntime => + { + // Arrange + var testDTO = new TestDTO { StringVal = "Hello", IntVal = 4 }; + var cts = new CancellationTokenSource(); + var task = jsRuntime.InvokeAsync("unimportant", cts.Token); + var argsJson = JsonSerializer.Serialize(new object[] { jsRuntime.LastInvocationAsyncHandle, true, testDTO }, JsonSerializerOptionsProvider.Options); + + // Act + cts.Cancel(); + DotNetDispatcher.EndInvoke(argsJson); + + // Assert + Assert.True(task.IsCanceled); + }); + + [Fact] + public Task EndInvoke_WithNullError() => WithJSRuntime(async jsRuntime => + { + // Arrange + var task = jsRuntime.InvokeAsync("unimportant"); + var argsJson = JsonSerializer.Serialize(new object[] { jsRuntime.LastInvocationAsyncHandle, false, null }, JsonSerializerOptionsProvider.Options); + + // Act + DotNetDispatcher.EndInvoke(argsJson); + + // Assert + var ex = await Assert.ThrowsAsync(() => task); + Assert.Empty(ex.Message); + }); + [Fact] public Task CanInvokeInstanceMethodWithParams() => WithJSRuntime(jsRuntime => { @@ -261,10 +326,14 @@ namespace Microsoft.JSInterop.Tests }); [Fact] - public void CannotInvokeWithIncorrectNumberOfParams() + public Task CannotInvokeWithFewerNumberOfParameters() => WithJSRuntime(jsRuntime => { // Arrange - var argsJson = JsonSerializer.Serialize(new object[] { 1, 2, 3, 4 }, JsonSerializerOptionsProvider.Options); + var argsJson = JsonSerializer.Serialize(new object[] + { + new TestDTO { StringVal = "Another string", IntVal = 456 }, + new[] { 100, 200 }, + }, JsonSerializerOptionsProvider.Options); // Act/Assert var ex = Assert.Throws(() => @@ -272,8 +341,30 @@ namespace Microsoft.JSInterop.Tests DotNetDispatcher.Invoke(thisAssemblyName, "InvocableStaticWithParams", default, argsJson); }); - Assert.Equal("In call to 'InvocableStaticWithParams', expected 3 parameters but received 4.", ex.Message); - } + Assert.Equal("The call to 'InvocableStaticWithParams' expects '3' parameters, but received '2'.", ex.Message); + }); + + [Fact] + public Task CannotInvokeWithMoreParameters() => WithJSRuntime(jsRuntime => + { + // Arrange + var objectRef = DotNetObjectRef.Create(new TestDTO { IntVal = 4 }); + var argsJson = JsonSerializer.Serialize(new object[] + { + new TestDTO { StringVal = "Another string", IntVal = 456 }, + new[] { 100, 200 }, + objectRef, + 7, + }, JsonSerializerOptionsProvider.Options); + + // Act/Assert + var ex = Assert.Throws(() => + { + DotNetDispatcher.Invoke(thisAssemblyName, "InvocableStaticWithParams", default, argsJson); + }); + + Assert.Equal("Unexpected JSON token Number. Ensure that the call to `InvocableStaticWithParams' is supplied with exactly '3' parameters.", ex.Message); + }); [Fact] public Task CanInvokeAsyncMethod() => WithJSRuntime(async jsRuntime => @@ -301,7 +392,7 @@ namespace Microsoft.JSInterop.Tests // Assert: Correct completion information Assert.Equal(callId, jsRuntime.LastCompletionCallId); Assert.True(jsRuntime.LastCompletionStatus); - var result = Assert.IsType(jsRuntime.LastCompletionResult); + var result = Assert.IsType(jsRuntime.LastCompletionResult); var resultDto1 = Assert.IsType(result[0]); Assert.Equal("STRING VIA JSON", resultDto1.StringVal); @@ -390,6 +481,150 @@ namespace Microsoft.JSInterop.Tests Assert.StartsWith("System.ArgumentException: There is no tracked object with id '1'. Perhaps the DotNetObjectRef instance was already disposed.", result.SourceException.ToString()); }); + [Theory] + [InlineData("")] + [InlineData("")] + public void ParseArguments_ThrowsIfJsonIsInvalid(string arguments) + { + Assert.ThrowsAny(() => DotNetDispatcher.ParseArguments("SomeMethod", arguments, new[] { typeof(string) })); + } + + [Theory] + [InlineData("{\"key\":\"value\"}")] + [InlineData("\"Test\"")] + public void ParseArguments_ThrowsIfTheArgsJsonIsNotArray(string arguments) + { + // Act & Assert + Assert.ThrowsAny(() => DotNetDispatcher.ParseArguments("SomeMethod", arguments, new[] { typeof(string) })); + } + + [Theory] + [InlineData("[\"hello\"")] + [InlineData("[\"hello\",")] + public void ParseArguments_ThrowsIfTheArgsJsonIsInvalidArray(string arguments) + { + // Act & Assert + Assert.ThrowsAny(() => DotNetDispatcher.ParseArguments("SomeMethod", arguments, new[] { typeof(string) })); + } + + [Fact] + public void ParseArguments_Works() + { + // Arrange + var arguments = "[\"Hello\", 2]"; + + // Act + var result = DotNetDispatcher.ParseArguments("SomeMethod", arguments, new[] { typeof(string), typeof(int), }); + + // Assert + Assert.Equal(new object[] { "Hello", 2 }, result); + } + + [Fact] + public void ParseArguments_SingleArgument() + { + // Arrange + var arguments = "[{\"IntVal\": 7}]"; + + // Act + var result = DotNetDispatcher.ParseArguments("SomeMethod", arguments, new[] { typeof(TestDTO), }); + + // Assert + var value = Assert.IsType(Assert.Single(result)); + Assert.Equal(7, value.IntVal); + Assert.Null(value.StringVal); + } + + [Fact] + public void ParseArguments_NullArgument() + { + // Arrange + var arguments = "[4, null]"; + + // Act + var result = DotNetDispatcher.ParseArguments("SomeMethod", arguments, new[] { typeof(int), typeof(TestDTO), }); + + // Assert + Assert.Collection( + result, + v => Assert.Equal(4, v), + v => Assert.Null(v)); + } + + [Fact] + public void ParseArguments_Throws_WithIncorrectDotNetObjectRefUsage() + { + // Arrange + var method = "SomeMethod"; + var arguments = "[4, {\"__dotNetObject\": 7}]"; + + // Act + var ex = Assert.Throws(() => DotNetDispatcher.ParseArguments(method, arguments, new[] { typeof(int), typeof(TestDTO), })); + + // Assert + Assert.Equal($"In call to '{method}', parameter of type '{nameof(TestDTO)}' at index 2 must be declared as type 'DotNetObjectRef' to receive the incoming value.", ex.Message); + } + + [Fact] + public void ParseEndInvokeArguments_ThrowsIfJsonIsEmptyString() + { + Assert.ThrowsAny(() => DotNetDispatcher.ParseEndInvokeArguments(new TestJSRuntime(), "")); + } + + [Fact] + public void ParseEndInvokeArguments_ThrowsIfJsonIsNotArray() + { + Assert.ThrowsAny(() => DotNetDispatcher.ParseEndInvokeArguments(new TestJSRuntime(), "{\"key\": \"value\"}")); + } + + [Fact] + public void ParseEndInvokeArguments_ThrowsIfJsonArrayIsInComplete() + { + Assert.ThrowsAny(() => DotNetDispatcher.ParseEndInvokeArguments(new TestJSRuntime(), "[7, false")); + } + + [Fact] + public void ParseEndInvokeArguments_ThrowsIfJsonArrayHasMoreThan3Arguments() + { + Assert.ThrowsAny(() => DotNetDispatcher.ParseEndInvokeArguments(new TestJSRuntime(), "[7, false, \"Hello\", 5]")); + } + + [Fact] + public void ParseEndInvokeArguments_Works() + { + var jsRuntime = new TestJSRuntime(); + var task = jsRuntime.InvokeAsync("somemethod"); + + DotNetDispatcher.ParseEndInvokeArguments(jsRuntime, $"[{jsRuntime.LastInvocationAsyncHandle}, true, {{\"intVal\": 7}}]"); + + Assert.True(task.IsCompletedSuccessfully); + Assert.Equal(7, task.Result.IntVal); + } + + [Fact] + public void ParseEndInvokeArguments_WithArrayValue() + { + var jsRuntime = new TestJSRuntime(); + var task = jsRuntime.InvokeAsync("somemethod"); + + DotNetDispatcher.ParseEndInvokeArguments(jsRuntime, $"[{jsRuntime.LastInvocationAsyncHandle}, true, [1, 2, 3]]"); + + Assert.True(task.IsCompletedSuccessfully); + Assert.Equal(new[] { 1, 2, 3 }, task.Result); + } + + [Fact] + public void ParseEndInvokeArguments_WithNullValue() + { + var jsRuntime = new TestJSRuntime(); + var task = jsRuntime.InvokeAsync("somemethod"); + + DotNetDispatcher.ParseEndInvokeArguments(jsRuntime, $"[{jsRuntime.LastInvocationAsyncHandle}, true, null]"); + + Assert.True(task.IsCompletedSuccessfully); + Assert.Null(task.Result); + } + Task WithJSRuntime(Action testCode) { return WithJSRuntime(jsRuntime => diff --git a/src/JSInterop/Microsoft.JSInterop/test/JSRuntimeBaseTest.cs b/src/JSInterop/Microsoft.JSInterop/test/JSRuntimeBaseTest.cs index fb9227c996..468f8efba3 100644 --- a/src/JSInterop/Microsoft.JSInterop/test/JSRuntimeBaseTest.cs +++ b/src/JSInterop/Microsoft.JSInterop/test/JSRuntimeBaseTest.cs @@ -4,13 +4,13 @@ using System; using System.Collections.Generic; using System.Linq; -using System.Runtime.ExceptionServices; +using System.Text; using System.Text.Json; using System.Threading; using System.Threading.Tasks; using Xunit; -namespace Microsoft.JSInterop.Tests +namespace Microsoft.JSInterop { public class JSRuntimeBaseTest { @@ -54,18 +54,19 @@ namespace Microsoft.JSInterop.Tests } [Fact] - public async Task InvokeAsync_CompletesSuccessfullyBeforeTimeout() + public void InvokeAsync_CompletesSuccessfullyBeforeTimeout() { // Arrange var runtime = new TestJSRuntime(); runtime.DefaultTimeout = TimeSpan.FromSeconds(10); + var reader = new Utf8JsonReader(Encoding.UTF8.GetBytes("null")); // Act var task = runtime.InvokeAsync("test identifier 1", "arg1", 123, true); - runtime.EndInvokeJS(2, succeeded: true, null); - // Assert - await task; + runtime.EndInvokeJS(2, succeeded: true, ref reader); + + Assert.True(task.IsCompletedSuccessfully); } [Fact] @@ -113,18 +114,62 @@ namespace Microsoft.JSInterop.Tests var task = runtime.InvokeAsync("test identifier", Array.Empty()); Assert.False(unrelatedTask.IsCompleted); Assert.False(task.IsCompleted); - using var jsonDocument = JsonDocument.Parse("\"my result\""); + var bytes = Encoding.UTF8.GetBytes("\"my result\""); + var reader = new Utf8JsonReader(bytes); // Act/Assert: Task can be completed - runtime.OnEndInvoke( + runtime.EndInvokeJS( runtime.BeginInvokeCalls[1].AsyncHandle, /* succeeded: */ true, - new JSAsyncCallResult(jsonDocument, jsonDocument.RootElement)); + ref reader); Assert.False(unrelatedTask.IsCompleted); Assert.True(task.IsCompleted); Assert.Equal("my result", task.Result); } + [Fact] + public void CanCompleteAsyncCallsWithComplexType() + { + // Arrange + var runtime = new TestJSRuntime(); + + var task = runtime.InvokeAsync("test identifier", Array.Empty()); + var bytes = Encoding.UTF8.GetBytes("{\"id\":10, \"name\": \"Test\"}"); + var reader = new Utf8JsonReader(bytes); + + // Act/Assert: Task can be completed + runtime.EndInvokeJS( + runtime.BeginInvokeCalls[0].AsyncHandle, + /* succeeded: */ true, + ref reader); + Assert.True(task.IsCompleted); + var poco = task.Result; + Assert.Equal(10, poco.Id); + Assert.Equal("Test", poco.Name); + } + + [Fact] + public void CanCompleteAsyncCallsWithComplexTypeUsingPropertyCasing() + { + // Arrange + var runtime = new TestJSRuntime(); + + var task = runtime.InvokeAsync("test identifier", Array.Empty()); + var bytes = Encoding.UTF8.GetBytes("{\"Id\":10, \"Name\": \"Test\"}"); + var reader = new Utf8JsonReader(bytes); + reader.Read(); + + // Act/Assert: Task can be completed + runtime.EndInvokeJS( + runtime.BeginInvokeCalls[0].AsyncHandle, + /* succeeded: */ true, + ref reader); + Assert.True(task.IsCompleted); + var poco = task.Result; + Assert.Equal(10, poco.Id); + Assert.Equal("Test", poco.Name); + } + [Fact] public void CanCompleteAsyncCallsAsFailure() { @@ -136,13 +181,15 @@ namespace Microsoft.JSInterop.Tests var task = runtime.InvokeAsync("test identifier", Array.Empty()); Assert.False(unrelatedTask.IsCompleted); Assert.False(task.IsCompleted); - using var jsonDocument = JsonDocument.Parse("\"This is a test exception\""); + var bytes = Encoding.UTF8.GetBytes("\"This is a test exception\""); + var reader = new Utf8JsonReader(bytes); + reader.Read(); // Act/Assert: Task can be failed - runtime.OnEndInvoke( + runtime.EndInvokeJS( runtime.BeginInvokeCalls[1].AsyncHandle, /* succeeded: */ false, - new JSAsyncCallResult(jsonDocument, jsonDocument.RootElement)); + ref reader); Assert.False(unrelatedTask.IsCompleted); Assert.True(task.IsCompleted); @@ -152,7 +199,7 @@ namespace Microsoft.JSInterop.Tests } [Fact] - public async Task CanCompleteAsyncCallsWithErrorsDuringDeserialization() + public Task CanCompleteAsyncCallsWithErrorsDuringDeserialization() { // Arrange var runtime = new TestJSRuntime(); @@ -162,24 +209,27 @@ namespace Microsoft.JSInterop.Tests var task = runtime.InvokeAsync("test identifier", Array.Empty()); Assert.False(unrelatedTask.IsCompleted); Assert.False(task.IsCompleted); - using var jsonDocument = JsonDocument.Parse("\"Not a string\""); + var bytes = Encoding.UTF8.GetBytes("Not a string"); + var reader = new Utf8JsonReader(bytes); // Act/Assert: Task can be failed - runtime.OnEndInvoke( + runtime.EndInvokeJS( runtime.BeginInvokeCalls[1].AsyncHandle, /* succeeded: */ true, - new JSAsyncCallResult(jsonDocument, jsonDocument.RootElement)); + ref reader); Assert.False(unrelatedTask.IsCompleted); - var jsException = await Assert.ThrowsAsync(() => task); - Assert.IsType(jsException.InnerException); + return AssertTask(); - // Verify we've disposed the JsonDocument. - Assert.Throws(() => jsonDocument.RootElement.ValueKind); + async Task AssertTask() + { + var jsException = await Assert.ThrowsAsync(() => task); + Assert.IsAssignableFrom(jsException.InnerException); + } } [Fact] - public async Task CompletingSameAsyncCallMoreThanOnce_IgnoresSecondResultAsync() + public Task CompletingSameAsyncCallMoreThanOnce_IgnoresSecondResultAsync() { // Arrange var runtime = new TestJSRuntime(); @@ -187,11 +237,19 @@ namespace Microsoft.JSInterop.Tests // Act/Assert var task = runtime.InvokeAsync("test identifier", Array.Empty()); var asyncHandle = runtime.BeginInvokeCalls[0].AsyncHandle; - runtime.OnEndInvoke(asyncHandle, true, new JSAsyncCallResult(JsonDocument.Parse("{}"), JsonDocument.Parse("{\"Message\": \"Some data\"}").RootElement.GetProperty("Message"))); - runtime.OnEndInvoke(asyncHandle, false, new JSAsyncCallResult(null, JsonDocument.Parse("{\"Message\": \"Exception\"}").RootElement.GetProperty("Message"))); + var firstReader = new Utf8JsonReader(Encoding.UTF8.GetBytes("\"Some data\"")); + var secondReader = new Utf8JsonReader(Encoding.UTF8.GetBytes("\"Exception\"")); - var result = await task; - Assert.Equal("Some data", result); + runtime.EndInvokeJS(asyncHandle, true, ref firstReader); + runtime.EndInvokeJS(asyncHandle, false, ref secondReader); + + return AssertTask(); + + async Task AssertTask() + { + var result = await task; + Assert.Equal("Some data", result); + } } [Fact] @@ -263,6 +321,13 @@ namespace Microsoft.JSInterop.Tests public string Message { get; set; } } + private class TestPoco + { + public int Id { get; set; } + + public string Name { get; set; } + } + class TestJSRuntime : JSRuntimeBase { public List BeginInvokeCalls = new List(); @@ -316,9 +381,6 @@ namespace Microsoft.JSInterop.Tests ArgsJson = argsJson, }); } - - public void OnEndInvoke(long asyncHandle, bool succeeded, JSAsyncCallResult callResult) - => EndInvokeJS(asyncHandle, succeeded, callResult); } } } From c79002bf38f2a08f496c645ee04e0a9084601f31 Mon Sep 17 00:00:00 2001 From: "dotnet-maestro[bot]" <42748379+dotnet-maestro[bot]@users.noreply.github.com> Date: Wed, 7 Aug 2019 20:14:57 +0000 Subject: [PATCH 0144/1101] [master] Update dependencies from aspnet/AspNetCore-Tooling (#12941) * Update dependencies from https://github.com/aspnet/AspNetCore-Tooling build 20190806.4 - Microsoft.NET.Sdk.Razor - 5.0.0-alpha1.19406.4 - Microsoft.CodeAnalysis.Razor - 5.0.0-alpha1.19406.4 - Microsoft.AspNetCore.Razor.Language - 5.0.0-alpha1.19406.4 - Microsoft.AspNetCore.Mvc.Razor.Extensions - 5.0.0-alpha1.19406.4 * React to RenderTreeBuilder namespace change --- eng/Version.Details.xml | 16 +++--- eng/Versions.props | 8 +-- .../test/ChildContentRazorIntegrationTest.cs | 7 +-- .../ComponentRenderingRazorIntegrationTest.cs | 2 +- .../GenericComponentRazorIntegrationTest.cs | 5 +- ...ft.AspNetCore.Components.netstandard2.0.cs | 56 +++++++++---------- .../Components/src/Auth/AuthorizeViewCore.cs | 2 +- .../Components/src/ComponentBase.cs | 2 +- src/Components/Components/src/PageDisplay.cs | 2 +- .../Components/src/ParameterAttribute.cs | 2 +- .../Components/src/RenderFragment.cs | 2 +- .../RenderTreeBuilder.cs | 3 +- .../Components/src/Routing/Router.cs | 2 +- .../Components/test/Auth/AuthorizeViewTest.cs | 1 + .../Auth/CascadingAuthenticationStateTest.cs | 1 + .../Components/test/CascadingParameterTest.cs | 1 + .../Components/test/ComponentBaseTest.cs | 1 + .../Components/test/PageDisplayTest.cs | 1 + .../test/ParameterViewTest.Assignment.cs | 1 + .../{ => Rendering}/RenderTreeBuilderTest.cs | 3 +- .../Shared/test/AutoRenderComponent.cs | 2 +- .../test/AutoRenderFragmentComponent.cs | 2 +- ...spNetCore.Components.Web.netstandard2.0.cs | 20 +++---- src/Components/Web/src/Forms/EditForm.cs | 2 +- src/Components/Web/src/Forms/InputCheckbox.cs | 2 +- src/Components/Web/src/Forms/InputDate.cs | 2 +- src/Components/Web/src/Forms/InputNumber.cs | 2 +- src/Components/Web/src/Forms/InputSelect.cs | 2 +- src/Components/Web/src/Forms/InputText.cs | 2 +- src/Components/Web/src/Forms/InputTextArea.cs | 2 +- .../Web/src/Forms/ValidationMessage.cs | 2 +- .../Web/src/Forms/ValidationSummary.cs | 2 +- src/Components/Web/src/Routing/NavLink.cs | 2 +- .../Web/test/Forms/InputBaseTest.cs | 1 + .../test/testassets/BasicTestApp/Index.razor | 2 +- .../BasicTestApp/MarkupBlockComponent.razor | 2 +- .../HtmlHelperComponentExtensionsTests.cs | 2 +- 37 files changed, 87 insertions(+), 82 deletions(-) rename src/Components/Components/src/{RenderTree => Rendering}/RenderTreeBuilder.cs (99%) rename src/Components/Components/test/{ => Rendering}/RenderTreeBuilderTest.cs (99%) diff --git a/eng/Version.Details.xml b/eng/Version.Details.xml index 2c9d224447..67294ca344 100644 --- a/eng/Version.Details.xml +++ b/eng/Version.Details.xml @@ -13,21 +13,21 @@ https://github.com/aspnet/Blazor b2c48dd8c9099f71908fac26089cbea2c76d06a1 - + https://github.com/aspnet/AspNetCore-Tooling - 65994cb6ffd2d8da87db74e2b3e34cb5e350aff0 + 2e70e0cbd7d8b952313d1114cd750befca2b1453 - + https://github.com/aspnet/AspNetCore-Tooling - 65994cb6ffd2d8da87db74e2b3e34cb5e350aff0 + 2e70e0cbd7d8b952313d1114cd750befca2b1453 - + https://github.com/aspnet/AspNetCore-Tooling - 65994cb6ffd2d8da87db74e2b3e34cb5e350aff0 + 2e70e0cbd7d8b952313d1114cd750befca2b1453 - + https://github.com/aspnet/AspNetCore-Tooling - 65994cb6ffd2d8da87db74e2b3e34cb5e350aff0 + 2e70e0cbd7d8b952313d1114cd750befca2b1453 https://github.com/aspnet/EntityFrameworkCore diff --git a/eng/Versions.props b/eng/Versions.props index c2e7110155..3ec1e39ae3 100644 --- a/eng/Versions.props +++ b/eng/Versions.props @@ -162,10 +162,10 @@ 3.0.0-preview9.19402.9 3.0.0-preview9.19402.9 - 5.0.0-alpha1.19381.2 - 5.0.0-alpha1.19381.2 - 5.0.0-alpha1.19381.2 - 5.0.0-alpha1.19381.2 + 5.0.0-alpha1.19406.4 + 5.0.0-alpha1.19406.4 + 5.0.0-alpha1.19406.4 + 5.0.0-alpha1.19406.4 - netstandard2.0 + netstandard2.0;netcoreapp3.0 + + + + + diff --git a/src/Configuration.KeyPerFile/ref/Microsoft.Extensions.Configuration.KeyPerFile.netcoreapp3.0.cs b/src/Configuration.KeyPerFile/ref/Microsoft.Extensions.Configuration.KeyPerFile.netcoreapp3.0.cs new file mode 100644 index 0000000000..e26ca1909d --- /dev/null +++ b/src/Configuration.KeyPerFile/ref/Microsoft.Extensions.Configuration.KeyPerFile.netcoreapp3.0.cs @@ -0,0 +1,29 @@ +// 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. + +namespace Microsoft.Extensions.Configuration +{ + public static partial class KeyPerFileConfigurationBuilderExtensions + { + public static Microsoft.Extensions.Configuration.IConfigurationBuilder AddKeyPerFile(this Microsoft.Extensions.Configuration.IConfigurationBuilder builder, System.Action configureSource) { throw null; } + public static Microsoft.Extensions.Configuration.IConfigurationBuilder AddKeyPerFile(this Microsoft.Extensions.Configuration.IConfigurationBuilder builder, string directoryPath, bool optional) { throw null; } + } +} +namespace Microsoft.Extensions.Configuration.KeyPerFile +{ + public partial class KeyPerFileConfigurationProvider : Microsoft.Extensions.Configuration.ConfigurationProvider + { + public KeyPerFileConfigurationProvider(Microsoft.Extensions.Configuration.KeyPerFile.KeyPerFileConfigurationSource source) { } + public override void Load() { } + public override string ToString() { throw null; } + } + public partial class KeyPerFileConfigurationSource : Microsoft.Extensions.Configuration.IConfigurationSource + { + public KeyPerFileConfigurationSource() { } + public Microsoft.Extensions.FileProviders.IFileProvider FileProvider { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } + public System.Func IgnoreCondition { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } + public string IgnorePrefix { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } + public bool Optional { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } + public Microsoft.Extensions.Configuration.IConfigurationProvider Build(Microsoft.Extensions.Configuration.IConfigurationBuilder builder) { throw null; } + } +} diff --git a/src/Configuration.KeyPerFile/src/Microsoft.Extensions.Configuration.KeyPerFile.csproj b/src/Configuration.KeyPerFile/src/Microsoft.Extensions.Configuration.KeyPerFile.csproj index 8c76c174ad..10f41fa9f4 100644 --- a/src/Configuration.KeyPerFile/src/Microsoft.Extensions.Configuration.KeyPerFile.csproj +++ b/src/Configuration.KeyPerFile/src/Microsoft.Extensions.Configuration.KeyPerFile.csproj @@ -2,7 +2,8 @@ Configuration provider that uses files in a directory for Microsoft.Extensions.Configuration. - netstandard2.0 + netstandard2.0;netcoreapp3.0 + netcoreapp3.0 true true diff --git a/src/FileProviders/Embedded/ref/Microsoft.Extensions.FileProviders.Embedded.csproj b/src/FileProviders/Embedded/ref/Microsoft.Extensions.FileProviders.Embedded.csproj index b8f2f33387..e9407f9164 100644 --- a/src/FileProviders/Embedded/ref/Microsoft.Extensions.FileProviders.Embedded.csproj +++ b/src/FileProviders/Embedded/ref/Microsoft.Extensions.FileProviders.Embedded.csproj @@ -1,10 +1,14 @@ - netstandard2.0 + netstandard2.0;netcoreapp3.0 + + + + diff --git a/src/FileProviders/Embedded/ref/Microsoft.Extensions.FileProviders.Embedded.netcoreapp3.0.cs b/src/FileProviders/Embedded/ref/Microsoft.Extensions.FileProviders.Embedded.netcoreapp3.0.cs new file mode 100644 index 0000000000..1596f191fd --- /dev/null +++ b/src/FileProviders/Embedded/ref/Microsoft.Extensions.FileProviders.Embedded.netcoreapp3.0.cs @@ -0,0 +1,39 @@ +// 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. + +namespace Microsoft.Extensions.FileProviders +{ + public partial class EmbeddedFileProvider : Microsoft.Extensions.FileProviders.IFileProvider + { + public EmbeddedFileProvider(System.Reflection.Assembly assembly) { } + public EmbeddedFileProvider(System.Reflection.Assembly assembly, string baseNamespace) { } + public Microsoft.Extensions.FileProviders.IDirectoryContents GetDirectoryContents(string subpath) { throw null; } + public Microsoft.Extensions.FileProviders.IFileInfo GetFileInfo(string subpath) { throw null; } + public Microsoft.Extensions.Primitives.IChangeToken Watch(string pattern) { throw null; } + } + public partial class ManifestEmbeddedFileProvider : Microsoft.Extensions.FileProviders.IFileProvider + { + public ManifestEmbeddedFileProvider(System.Reflection.Assembly assembly) { } + public ManifestEmbeddedFileProvider(System.Reflection.Assembly assembly, string root) { } + public ManifestEmbeddedFileProvider(System.Reflection.Assembly assembly, string root, System.DateTimeOffset lastModified) { } + public ManifestEmbeddedFileProvider(System.Reflection.Assembly assembly, string root, string manifestName, System.DateTimeOffset lastModified) { } + public System.Reflection.Assembly Assembly { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } } + public Microsoft.Extensions.FileProviders.IDirectoryContents GetDirectoryContents(string subpath) { throw null; } + public Microsoft.Extensions.FileProviders.IFileInfo GetFileInfo(string subpath) { throw null; } + public Microsoft.Extensions.Primitives.IChangeToken Watch(string filter) { throw null; } + } +} +namespace Microsoft.Extensions.FileProviders.Embedded +{ + public partial class EmbeddedResourceFileInfo : Microsoft.Extensions.FileProviders.IFileInfo + { + public EmbeddedResourceFileInfo(System.Reflection.Assembly assembly, string resourcePath, string name, System.DateTimeOffset lastModified) { } + public bool Exists { get { throw null; } } + public bool IsDirectory { get { throw null; } } + public System.DateTimeOffset LastModified { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } } + public long Length { get { throw null; } } + public string Name { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } } + public string PhysicalPath { get { throw null; } } + public System.IO.Stream CreateReadStream() { throw null; } + } +} diff --git a/src/FileProviders/Embedded/src/Microsoft.Extensions.FileProviders.Embedded.csproj b/src/FileProviders/Embedded/src/Microsoft.Extensions.FileProviders.Embedded.csproj index 8119c26339..115d9affe1 100644 --- a/src/FileProviders/Embedded/src/Microsoft.Extensions.FileProviders.Embedded.csproj +++ b/src/FileProviders/Embedded/src/Microsoft.Extensions.FileProviders.Embedded.csproj @@ -3,8 +3,10 @@ Microsoft.Extensions.FileProviders File provider for files in embedded resources for Microsoft.Extensions.FileProviders. - netstandard2.0 - $(MSBuildProjectName).nuspec + netstandard2.0;netcoreapp3.0 + $(MSBuildProjectName).multitarget.nuspec + netcoreapp3.0 + $(MSBuildProjectName).netcoreapp3.0.nuspec true true @@ -23,21 +25,12 @@ - - - <_OutputBinary>@(BuiltProjectOutputGroupOutput) - <_OutputSymbol>@(DebugSymbolsProjectOutputGroupOutput) - <_OutputDocumentation>@(DocumentationProjectOutputGroupOutput) - - - - - - - - - - - - + + + + + + + + diff --git a/src/FileProviders/Embedded/src/Microsoft.Extensions.FileProviders.Embedded.multitarget.nuspec b/src/FileProviders/Embedded/src/Microsoft.Extensions.FileProviders.Embedded.multitarget.nuspec new file mode 100644 index 0000000000..874c90c79d --- /dev/null +++ b/src/FileProviders/Embedded/src/Microsoft.Extensions.FileProviders.Embedded.multitarget.nuspec @@ -0,0 +1,24 @@ + + + + $CommonMetadataElements$ + + + + + + + + + + + + + + + + + + + + diff --git a/src/FileProviders/Embedded/src/Microsoft.Extensions.FileProviders.Embedded.nuspec b/src/FileProviders/Embedded/src/Microsoft.Extensions.FileProviders.Embedded.netcoreapp3.0.nuspec similarity index 73% rename from src/FileProviders/Embedded/src/Microsoft.Extensions.FileProviders.Embedded.nuspec rename to src/FileProviders/Embedded/src/Microsoft.Extensions.FileProviders.Embedded.netcoreapp3.0.nuspec index 4a33eb6a95..f98f0b4ad5 100644 --- a/src/FileProviders/Embedded/src/Microsoft.Extensions.FileProviders.Embedded.nuspec +++ b/src/FileProviders/Embedded/src/Microsoft.Extensions.FileProviders.Embedded.netcoreapp3.0.nuspec @@ -3,16 +3,16 @@ $CommonMetadataElements$ - + - - - + + + diff --git a/src/HealthChecks/Abstractions/ref/Microsoft.Extensions.Diagnostics.HealthChecks.Abstractions.csproj b/src/HealthChecks/Abstractions/ref/Microsoft.Extensions.Diagnostics.HealthChecks.Abstractions.csproj index be23858955..f7514489fa 100644 --- a/src/HealthChecks/Abstractions/ref/Microsoft.Extensions.Diagnostics.HealthChecks.Abstractions.csproj +++ b/src/HealthChecks/Abstractions/ref/Microsoft.Extensions.Diagnostics.HealthChecks.Abstractions.csproj @@ -1,10 +1,14 @@ - netstandard2.0 + netstandard2.0;netcoreapp3.0 + + + + diff --git a/src/HealthChecks/Abstractions/ref/Microsoft.Extensions.Diagnostics.HealthChecks.Abstractions.netcoreapp3.0.cs b/src/HealthChecks/Abstractions/ref/Microsoft.Extensions.Diagnostics.HealthChecks.Abstractions.netcoreapp3.0.cs new file mode 100644 index 0000000000..8c53adc275 --- /dev/null +++ b/src/HealthChecks/Abstractions/ref/Microsoft.Extensions.Diagnostics.HealthChecks.Abstractions.netcoreapp3.0.cs @@ -0,0 +1,72 @@ +// 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. + +namespace Microsoft.Extensions.Diagnostics.HealthChecks +{ + public sealed partial class HealthCheckContext + { + public HealthCheckContext() { } + public Microsoft.Extensions.Diagnostics.HealthChecks.HealthCheckRegistration Registration { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } + } + public sealed partial class HealthCheckRegistration + { + public HealthCheckRegistration(string name, Microsoft.Extensions.Diagnostics.HealthChecks.IHealthCheck instance, Microsoft.Extensions.Diagnostics.HealthChecks.HealthStatus? failureStatus, System.Collections.Generic.IEnumerable tags) { } + public HealthCheckRegistration(string name, Microsoft.Extensions.Diagnostics.HealthChecks.IHealthCheck instance, Microsoft.Extensions.Diagnostics.HealthChecks.HealthStatus? failureStatus, System.Collections.Generic.IEnumerable tags, System.TimeSpan? timeout) { } + public HealthCheckRegistration(string name, System.Func factory, Microsoft.Extensions.Diagnostics.HealthChecks.HealthStatus? failureStatus, System.Collections.Generic.IEnumerable tags) { } + public HealthCheckRegistration(string name, System.Func factory, Microsoft.Extensions.Diagnostics.HealthChecks.HealthStatus? failureStatus, System.Collections.Generic.IEnumerable tags, System.TimeSpan? timeout) { } + public System.Func Factory { get { throw null; } set { } } + public Microsoft.Extensions.Diagnostics.HealthChecks.HealthStatus FailureStatus { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } + public string Name { get { throw null; } set { } } + public System.Collections.Generic.ISet Tags { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } } + public System.TimeSpan Timeout { get { throw null; } set { } } + } + [System.Runtime.InteropServices.StructLayoutAttribute(System.Runtime.InteropServices.LayoutKind.Sequential)] + public partial struct HealthCheckResult + { + private object _dummy; + private int _dummyPrimitive; + public HealthCheckResult(Microsoft.Extensions.Diagnostics.HealthChecks.HealthStatus status, string description = null, System.Exception exception = null, System.Collections.Generic.IReadOnlyDictionary data = null) { throw null; } + public System.Collections.Generic.IReadOnlyDictionary Data { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } } + public string Description { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } } + public System.Exception Exception { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } } + public Microsoft.Extensions.Diagnostics.HealthChecks.HealthStatus Status { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } } + public static Microsoft.Extensions.Diagnostics.HealthChecks.HealthCheckResult Degraded(string description = null, System.Exception exception = null, System.Collections.Generic.IReadOnlyDictionary data = null) { throw null; } + public static Microsoft.Extensions.Diagnostics.HealthChecks.HealthCheckResult Healthy(string description = null, System.Collections.Generic.IReadOnlyDictionary data = null) { throw null; } + public static Microsoft.Extensions.Diagnostics.HealthChecks.HealthCheckResult Unhealthy(string description = null, System.Exception exception = null, System.Collections.Generic.IReadOnlyDictionary data = null) { throw null; } + } + public sealed partial class HealthReport + { + public HealthReport(System.Collections.Generic.IReadOnlyDictionary entries, System.TimeSpan totalDuration) { } + public System.Collections.Generic.IReadOnlyDictionary Entries { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } } + public Microsoft.Extensions.Diagnostics.HealthChecks.HealthStatus Status { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } } + public System.TimeSpan TotalDuration { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } } + } + [System.Runtime.InteropServices.StructLayoutAttribute(System.Runtime.InteropServices.LayoutKind.Sequential)] + public partial struct HealthReportEntry + { + private object _dummy; + private int _dummyPrimitive; + public HealthReportEntry(Microsoft.Extensions.Diagnostics.HealthChecks.HealthStatus status, string description, System.TimeSpan duration, System.Exception exception, System.Collections.Generic.IReadOnlyDictionary data) { throw null; } + public HealthReportEntry(Microsoft.Extensions.Diagnostics.HealthChecks.HealthStatus status, string description, System.TimeSpan duration, System.Exception exception, System.Collections.Generic.IReadOnlyDictionary data, System.Collections.Generic.IEnumerable tags = null) { throw null; } + public System.Collections.Generic.IReadOnlyDictionary Data { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } } + public string Description { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } } + public System.TimeSpan Duration { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } } + public System.Exception Exception { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } } + public Microsoft.Extensions.Diagnostics.HealthChecks.HealthStatus Status { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } } + public System.Collections.Generic.IEnumerable Tags { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } } + } + public enum HealthStatus + { + Unhealthy = 0, + Degraded = 1, + Healthy = 2, + } + public partial interface IHealthCheck + { + System.Threading.Tasks.Task CheckHealthAsync(Microsoft.Extensions.Diagnostics.HealthChecks.HealthCheckContext context, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)); + } + public partial interface IHealthCheckPublisher + { + System.Threading.Tasks.Task PublishAsync(Microsoft.Extensions.Diagnostics.HealthChecks.HealthReport report, System.Threading.CancellationToken cancellationToken); + } +} diff --git a/src/HealthChecks/Abstractions/src/Microsoft.Extensions.Diagnostics.HealthChecks.Abstractions.csproj b/src/HealthChecks/Abstractions/src/Microsoft.Extensions.Diagnostics.HealthChecks.Abstractions.csproj index 306a303421..f41b257c63 100644 --- a/src/HealthChecks/Abstractions/src/Microsoft.Extensions.Diagnostics.HealthChecks.Abstractions.csproj +++ b/src/HealthChecks/Abstractions/src/Microsoft.Extensions.Diagnostics.HealthChecks.Abstractions.csproj @@ -7,7 +7,8 @@ Commonly Used Types Microsoft.Extensions.Diagnostics.HealthChecks.IHealthCheck Microsoft.Extensions.Diagnostics.HealthChecks - netstandard2.0 + netstandard2.0;netcoreapp3.0 + netcoreapp3.0 $(NoWarn);CS1591 true diagnostics;healthchecks diff --git a/src/HealthChecks/HealthChecks/ref/Microsoft.Extensions.Diagnostics.HealthChecks.csproj b/src/HealthChecks/HealthChecks/ref/Microsoft.Extensions.Diagnostics.HealthChecks.csproj index 277e60910f..2286087bc7 100644 --- a/src/HealthChecks/HealthChecks/ref/Microsoft.Extensions.Diagnostics.HealthChecks.csproj +++ b/src/HealthChecks/HealthChecks/ref/Microsoft.Extensions.Diagnostics.HealthChecks.csproj @@ -1,7 +1,7 @@ - netstandard2.0 + netstandard2.0;netcoreapp3.0 @@ -9,4 +9,10 @@ + + + + + + diff --git a/src/HealthChecks/HealthChecks/ref/Microsoft.Extensions.Diagnostics.HealthChecks.netcoreapp3.0.cs b/src/HealthChecks/HealthChecks/ref/Microsoft.Extensions.Diagnostics.HealthChecks.netcoreapp3.0.cs new file mode 100644 index 0000000000..a23961efdd --- /dev/null +++ b/src/HealthChecks/HealthChecks/ref/Microsoft.Extensions.Diagnostics.HealthChecks.netcoreapp3.0.cs @@ -0,0 +1,59 @@ +// 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. + +namespace Microsoft.Extensions.DependencyInjection +{ + public static partial class HealthChecksBuilderAddCheckExtensions + { + public static Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder AddCheck(this Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder builder, string name, Microsoft.Extensions.Diagnostics.HealthChecks.IHealthCheck instance, Microsoft.Extensions.Diagnostics.HealthChecks.HealthStatus? failureStatus, System.Collections.Generic.IEnumerable tags) { throw null; } + public static Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder AddCheck(this Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder builder, string name, Microsoft.Extensions.Diagnostics.HealthChecks.IHealthCheck instance, Microsoft.Extensions.Diagnostics.HealthChecks.HealthStatus? failureStatus = default(Microsoft.Extensions.Diagnostics.HealthChecks.HealthStatus?), System.Collections.Generic.IEnumerable tags = null, System.TimeSpan? timeout = default(System.TimeSpan?)) { throw null; } + public static Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder AddCheck(this Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder builder, string name, Microsoft.Extensions.Diagnostics.HealthChecks.HealthStatus? failureStatus, System.Collections.Generic.IEnumerable tags) where T : class, Microsoft.Extensions.Diagnostics.HealthChecks.IHealthCheck { throw null; } + public static Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder AddCheck(this Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder builder, string name, Microsoft.Extensions.Diagnostics.HealthChecks.HealthStatus? failureStatus = default(Microsoft.Extensions.Diagnostics.HealthChecks.HealthStatus?), System.Collections.Generic.IEnumerable tags = null, System.TimeSpan? timeout = default(System.TimeSpan?)) where T : class, Microsoft.Extensions.Diagnostics.HealthChecks.IHealthCheck { throw null; } + public static Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder AddTypeActivatedCheck(this Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder builder, string name, Microsoft.Extensions.Diagnostics.HealthChecks.HealthStatus? failureStatus, System.Collections.Generic.IEnumerable tags, params object[] args) where T : class, Microsoft.Extensions.Diagnostics.HealthChecks.IHealthCheck { throw null; } + public static Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder AddTypeActivatedCheck(this Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder builder, string name, Microsoft.Extensions.Diagnostics.HealthChecks.HealthStatus? failureStatus, System.Collections.Generic.IEnumerable tags, System.TimeSpan timeout, params object[] args) where T : class, Microsoft.Extensions.Diagnostics.HealthChecks.IHealthCheck { throw null; } + public static Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder AddTypeActivatedCheck(this Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder builder, string name, Microsoft.Extensions.Diagnostics.HealthChecks.HealthStatus? failureStatus, params object[] args) where T : class, Microsoft.Extensions.Diagnostics.HealthChecks.IHealthCheck { throw null; } + public static Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder AddTypeActivatedCheck(this Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder builder, string name, params object[] args) where T : class, Microsoft.Extensions.Diagnostics.HealthChecks.IHealthCheck { throw null; } + } + public static partial class HealthChecksBuilderDelegateExtensions + { + public static Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder AddAsyncCheck(this Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder builder, string name, System.Func> check, System.Collections.Generic.IEnumerable tags) { throw null; } + public static Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder AddAsyncCheck(this Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder builder, string name, System.Func> check, System.Collections.Generic.IEnumerable tags = null, System.TimeSpan? timeout = default(System.TimeSpan?)) { throw null; } + public static Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder AddAsyncCheck(this Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder builder, string name, System.Func> check, System.Collections.Generic.IEnumerable tags) { throw null; } + public static Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder AddAsyncCheck(this Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder builder, string name, System.Func> check, System.Collections.Generic.IEnumerable tags = null, System.TimeSpan? timeout = default(System.TimeSpan?)) { throw null; } + public static Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder AddCheck(this Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder builder, string name, System.Func check, System.Collections.Generic.IEnumerable tags) { throw null; } + public static Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder AddCheck(this Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder builder, string name, System.Func check, System.Collections.Generic.IEnumerable tags = null, System.TimeSpan? timeout = default(System.TimeSpan?)) { throw null; } + public static Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder AddCheck(this Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder builder, string name, System.Func check, System.Collections.Generic.IEnumerable tags) { throw null; } + public static Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder AddCheck(this Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder builder, string name, System.Func check, System.Collections.Generic.IEnumerable tags = null, System.TimeSpan? timeout = default(System.TimeSpan?)) { throw null; } + } + public static partial class HealthCheckServiceCollectionExtensions + { + public static Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder AddHealthChecks(this Microsoft.Extensions.DependencyInjection.IServiceCollection services) { throw null; } + } + public partial interface IHealthChecksBuilder + { + Microsoft.Extensions.DependencyInjection.IServiceCollection Services { get; } + Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder Add(Microsoft.Extensions.Diagnostics.HealthChecks.HealthCheckRegistration registration); + } +} +namespace Microsoft.Extensions.Diagnostics.HealthChecks +{ + public sealed partial class HealthCheckPublisherOptions + { + public HealthCheckPublisherOptions() { } + public System.TimeSpan Delay { get { throw null; } set { } } + public System.TimeSpan Period { get { throw null; } set { } } + public System.Func Predicate { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } + public System.TimeSpan Timeout { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } + } + public abstract partial class HealthCheckService + { + protected HealthCheckService() { } + public abstract System.Threading.Tasks.Task CheckHealthAsync(System.Func predicate, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)); + public System.Threading.Tasks.Task CheckHealthAsync(System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) { throw null; } + } + public sealed partial class HealthCheckServiceOptions + { + public HealthCheckServiceOptions() { } + public System.Collections.Generic.ICollection Registrations { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } } + } +} diff --git a/src/HealthChecks/HealthChecks/src/Microsoft.Extensions.Diagnostics.HealthChecks.csproj b/src/HealthChecks/HealthChecks/src/Microsoft.Extensions.Diagnostics.HealthChecks.csproj index 9670e8bb7b..4005685011 100644 --- a/src/HealthChecks/HealthChecks/src/Microsoft.Extensions.Diagnostics.HealthChecks.csproj +++ b/src/HealthChecks/HealthChecks/src/Microsoft.Extensions.Diagnostics.HealthChecks.csproj @@ -6,7 +6,8 @@ Commonly Used Types: Microsoft.Extensions.Diagnostics.HealthChecks.HealthCheckService Microsoft.Extensions.Diagnostics.HealthChecks.IHealthChecksBuilder - netstandard2.0 + netstandard2.0;netcoreapp3.0 + netcoreapp3.0 $(NoWarn);CS1591 true diagnostics;healthchecks diff --git a/src/JSInterop/Microsoft.JSInterop/ref/Microsoft.JSInterop.csproj b/src/JSInterop/Microsoft.JSInterop/ref/Microsoft.JSInterop.csproj index 5f83c53091..61980aa920 100644 --- a/src/JSInterop/Microsoft.JSInterop/ref/Microsoft.JSInterop.csproj +++ b/src/JSInterop/Microsoft.JSInterop/ref/Microsoft.JSInterop.csproj @@ -1,10 +1,14 @@ - netstandard2.0 + netstandard2.0;netcoreapp3.0 + + + + diff --git a/src/JSInterop/Microsoft.JSInterop/ref/Microsoft.JSInterop.netcoreapp3.0.cs b/src/JSInterop/Microsoft.JSInterop/ref/Microsoft.JSInterop.netcoreapp3.0.cs new file mode 100644 index 0000000000..654ae9d617 --- /dev/null +++ b/src/JSInterop/Microsoft.JSInterop/ref/Microsoft.JSInterop.netcoreapp3.0.cs @@ -0,0 +1,64 @@ +// 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. + +namespace Microsoft.JSInterop +{ + public static partial class DotNetDispatcher + { + public static void BeginInvoke(string callId, string assemblyName, string methodIdentifier, long dotNetObjectId, string argsJson) { } + public static void EndInvoke(string arguments) { } + public static string Invoke(string assemblyName, string methodIdentifier, long dotNetObjectId, string argsJson) { throw null; } + [Microsoft.JSInterop.JSInvokableAttribute("DotNetDispatcher.ReleaseDotNetObject")] + public static void ReleaseDotNetObject(long dotNetObjectId) { } + } + public static partial class DotNetObjectRef + { + public static Microsoft.JSInterop.DotNetObjectRef Create(TValue value) where TValue : class { throw null; } + } + public sealed partial class DotNetObjectRef : System.IDisposable where TValue : class + { + internal DotNetObjectRef() { } + public TValue Value { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } } + public void Dispose() { } + } + public partial interface IJSInProcessRuntime : Microsoft.JSInterop.IJSRuntime + { + T Invoke(string identifier, params object[] args); + } + public partial interface IJSRuntime + { + System.Threading.Tasks.Task InvokeAsync(string identifier, System.Collections.Generic.IEnumerable args, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)); + System.Threading.Tasks.Task InvokeAsync(string identifier, params object[] args); + } + public partial class JSException : System.Exception + { + public JSException(string message) { } + public JSException(string message, System.Exception innerException) { } + } + public abstract partial class JSInProcessRuntimeBase : Microsoft.JSInterop.JSRuntimeBase, Microsoft.JSInterop.IJSInProcessRuntime, Microsoft.JSInterop.IJSRuntime + { + protected JSInProcessRuntimeBase() { } + protected abstract string InvokeJS(string identifier, string argsJson); + public TValue Invoke(string identifier, params object[] args) { throw null; } + } + [System.AttributeUsageAttribute(System.AttributeTargets.Method, AllowMultiple=true)] + public partial class JSInvokableAttribute : System.Attribute + { + public JSInvokableAttribute() { } + public JSInvokableAttribute(string identifier) { } + public string Identifier { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } } + } + public static partial class JSRuntime + { + public static void SetCurrentJSRuntime(Microsoft.JSInterop.IJSRuntime instance) { } + } + public abstract partial class JSRuntimeBase : Microsoft.JSInterop.IJSRuntime + { + protected JSRuntimeBase() { } + protected System.TimeSpan? DefaultAsyncTimeout { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } + protected abstract void BeginInvokeJS(long taskId, string identifier, string argsJson); + protected internal abstract void EndInvokeDotNet(string callId, bool success, object resultOrError, string assemblyName, string methodIdentifier, long dotNetObjectId); + public System.Threading.Tasks.Task InvokeAsync(string identifier, System.Collections.Generic.IEnumerable args, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) { throw null; } + public System.Threading.Tasks.Task InvokeAsync(string identifier, params object[] args) { throw null; } + } +} diff --git a/src/JSInterop/Microsoft.JSInterop/src/Microsoft.JSInterop.csproj b/src/JSInterop/Microsoft.JSInterop/src/Microsoft.JSInterop.csproj index 8dfb1ef22e..044c843b28 100644 --- a/src/JSInterop/Microsoft.JSInterop/src/Microsoft.JSInterop.csproj +++ b/src/JSInterop/Microsoft.JSInterop/src/Microsoft.JSInterop.csproj @@ -1,7 +1,8 @@  - netstandard2.0 + netstandard2.0;netcoreapp3.0 + netcoreapp3.0 Abstractions and features for interop between .NET and JavaScript code. javascript;interop true @@ -9,7 +10,7 @@ true - + diff --git a/src/JSInterop/Microsoft.JSInterop/test/Microsoft.JSInterop.Tests.csproj b/src/JSInterop/Microsoft.JSInterop/test/Microsoft.JSInterop.Tests.csproj index 34ac70b801..bff8fb3f99 100644 --- a/src/JSInterop/Microsoft.JSInterop/test/Microsoft.JSInterop.Tests.csproj +++ b/src/JSInterop/Microsoft.JSInterop/test/Microsoft.JSInterop.Tests.csproj @@ -1,7 +1,7 @@  - netcoreapp3.0 + netcoreapp3.0;net472 diff --git a/src/JSInterop/Mono.WebAssembly.Interop/src/Mono.WebAssembly.Interop.csproj b/src/JSInterop/Mono.WebAssembly.Interop/src/Mono.WebAssembly.Interop.csproj index fc92708456..f85c89a6d3 100644 --- a/src/JSInterop/Mono.WebAssembly.Interop/src/Mono.WebAssembly.Interop.csproj +++ b/src/JSInterop/Mono.WebAssembly.Interop/src/Mono.WebAssembly.Interop.csproj @@ -7,6 +7,8 @@ true true true + + true diff --git a/src/Localization/Abstractions/ref/Microsoft.Extensions.Localization.Abstractions.csproj b/src/Localization/Abstractions/ref/Microsoft.Extensions.Localization.Abstractions.csproj index 0608c06147..6404d5ae8e 100644 --- a/src/Localization/Abstractions/ref/Microsoft.Extensions.Localization.Abstractions.csproj +++ b/src/Localization/Abstractions/ref/Microsoft.Extensions.Localization.Abstractions.csproj @@ -1,10 +1,14 @@ - netstandard2.0 + netstandard2.0;netcoreapp3.0 + + + + diff --git a/src/Localization/Abstractions/ref/Microsoft.Extensions.Localization.Abstractions.netcoreapp3.0.cs b/src/Localization/Abstractions/ref/Microsoft.Extensions.Localization.Abstractions.netcoreapp3.0.cs new file mode 100644 index 0000000000..174cac28e5 --- /dev/null +++ b/src/Localization/Abstractions/ref/Microsoft.Extensions.Localization.Abstractions.netcoreapp3.0.cs @@ -0,0 +1,49 @@ +// 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. + +namespace Microsoft.Extensions.Localization +{ + public partial interface IStringLocalizer + { + Microsoft.Extensions.Localization.LocalizedString this[string name] { get; } + Microsoft.Extensions.Localization.LocalizedString this[string name, params object[] arguments] { get; } + System.Collections.Generic.IEnumerable GetAllStrings(bool includeParentCultures); + [System.ObsoleteAttribute("This method is obsolete. Use `CurrentCulture` and `CurrentUICulture` instead.")] + Microsoft.Extensions.Localization.IStringLocalizer WithCulture(System.Globalization.CultureInfo culture); + } + public partial interface IStringLocalizerFactory + { + Microsoft.Extensions.Localization.IStringLocalizer Create(string baseName, string location); + Microsoft.Extensions.Localization.IStringLocalizer Create(System.Type resourceSource); + } + public partial interface IStringLocalizer : Microsoft.Extensions.Localization.IStringLocalizer + { + } + public partial class LocalizedString + { + public LocalizedString(string name, string value) { } + public LocalizedString(string name, string value, bool resourceNotFound) { } + public LocalizedString(string name, string value, bool resourceNotFound, string searchedLocation) { } + public string Name { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } } + public bool ResourceNotFound { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } } + public string SearchedLocation { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } } + public string Value { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } } + public static implicit operator string (Microsoft.Extensions.Localization.LocalizedString localizedString) { throw null; } + public override string ToString() { throw null; } + } + public static partial class StringLocalizerExtensions + { + public static System.Collections.Generic.IEnumerable GetAllStrings(this Microsoft.Extensions.Localization.IStringLocalizer stringLocalizer) { throw null; } + public static Microsoft.Extensions.Localization.LocalizedString GetString(this Microsoft.Extensions.Localization.IStringLocalizer stringLocalizer, string name) { throw null; } + public static Microsoft.Extensions.Localization.LocalizedString GetString(this Microsoft.Extensions.Localization.IStringLocalizer stringLocalizer, string name, params object[] arguments) { throw null; } + } + public partial class StringLocalizer : Microsoft.Extensions.Localization.IStringLocalizer, Microsoft.Extensions.Localization.IStringLocalizer + { + public StringLocalizer(Microsoft.Extensions.Localization.IStringLocalizerFactory factory) { } + public virtual Microsoft.Extensions.Localization.LocalizedString this[string name] { get { throw null; } } + public virtual Microsoft.Extensions.Localization.LocalizedString this[string name, params object[] arguments] { get { throw null; } } + public System.Collections.Generic.IEnumerable GetAllStrings(bool includeParentCultures) { throw null; } + [System.ObsoleteAttribute("This method is obsolete. Use `CurrentCulture` and `CurrentUICulture` instead.")] + public virtual Microsoft.Extensions.Localization.IStringLocalizer WithCulture(System.Globalization.CultureInfo culture) { throw null; } + } +} diff --git a/src/Localization/Abstractions/src/Microsoft.Extensions.Localization.Abstractions.csproj b/src/Localization/Abstractions/src/Microsoft.Extensions.Localization.Abstractions.csproj index 86af6dfde9..09b8bf65b7 100644 --- a/src/Localization/Abstractions/src/Microsoft.Extensions.Localization.Abstractions.csproj +++ b/src/Localization/Abstractions/src/Microsoft.Extensions.Localization.Abstractions.csproj @@ -6,7 +6,8 @@ Commonly used types: Microsoft.Extensions.Localization.IStringLocalizer Microsoft.Extensions.Localization.IStringLocalizer<T> - netstandard2.0 + netstandard2.0;netcoreapp3.0 + netcoreapp3.0 $(NoWarn);CS1591 true localization diff --git a/src/Localization/Localization/ref/Microsoft.Extensions.Localization.csproj b/src/Localization/Localization/ref/Microsoft.Extensions.Localization.csproj index d628c33a54..735277e754 100644 --- a/src/Localization/Localization/ref/Microsoft.Extensions.Localization.csproj +++ b/src/Localization/Localization/ref/Microsoft.Extensions.Localization.csproj @@ -1,7 +1,7 @@ - netstandard2.0 + netstandard2.0;netcoreapp3.0 @@ -10,4 +10,11 @@ + + + + + + + diff --git a/src/Localization/Localization/ref/Microsoft.Extensions.Localization.netcoreapp3.0.cs b/src/Localization/Localization/ref/Microsoft.Extensions.Localization.netcoreapp3.0.cs new file mode 100644 index 0000000000..80175da718 --- /dev/null +++ b/src/Localization/Localization/ref/Microsoft.Extensions.Localization.netcoreapp3.0.cs @@ -0,0 +1,93 @@ +// 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. + +namespace Microsoft.Extensions.DependencyInjection +{ + public static partial class LocalizationServiceCollectionExtensions + { + public static Microsoft.Extensions.DependencyInjection.IServiceCollection AddLocalization(this Microsoft.Extensions.DependencyInjection.IServiceCollection services) { throw null; } + public static Microsoft.Extensions.DependencyInjection.IServiceCollection AddLocalization(this Microsoft.Extensions.DependencyInjection.IServiceCollection services, System.Action setupAction) { throw null; } + } +} +namespace Microsoft.Extensions.Localization +{ + public partial interface IResourceNamesCache + { + System.Collections.Generic.IList GetOrAdd(string name, System.Func> valueFactory); + } + public partial class LocalizationOptions + { + public LocalizationOptions() { } + public string ResourcesPath { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } + } + [System.AttributeUsageAttribute(System.AttributeTargets.Assembly, AllowMultiple=false, Inherited=false)] + public partial class ResourceLocationAttribute : System.Attribute + { + public ResourceLocationAttribute(string resourceLocation) { } + public string ResourceLocation { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } } + } + public partial class ResourceManagerStringLocalizer : Microsoft.Extensions.Localization.IStringLocalizer + { + public ResourceManagerStringLocalizer(System.Resources.ResourceManager resourceManager, Microsoft.Extensions.Localization.Internal.AssemblyWrapper resourceAssemblyWrapper, string baseName, Microsoft.Extensions.Localization.IResourceNamesCache resourceNamesCache, Microsoft.Extensions.Logging.ILogger logger) { } + public ResourceManagerStringLocalizer(System.Resources.ResourceManager resourceManager, Microsoft.Extensions.Localization.Internal.IResourceStringProvider resourceStringProvider, string baseName, Microsoft.Extensions.Localization.IResourceNamesCache resourceNamesCache, Microsoft.Extensions.Logging.ILogger logger) { } + public ResourceManagerStringLocalizer(System.Resources.ResourceManager resourceManager, System.Reflection.Assembly resourceAssembly, string baseName, Microsoft.Extensions.Localization.IResourceNamesCache resourceNamesCache, Microsoft.Extensions.Logging.ILogger logger) { } + public virtual Microsoft.Extensions.Localization.LocalizedString this[string name] { get { throw null; } } + public virtual Microsoft.Extensions.Localization.LocalizedString this[string name, params object[] arguments] { get { throw null; } } + public virtual System.Collections.Generic.IEnumerable GetAllStrings(bool includeParentCultures) { throw null; } + protected System.Collections.Generic.IEnumerable GetAllStrings(bool includeParentCultures, System.Globalization.CultureInfo culture) { throw null; } + protected string GetStringSafely(string name, System.Globalization.CultureInfo culture) { throw null; } + [System.ObsoleteAttribute("This method is obsolete. Use `CurrentCulture` and `CurrentUICulture` instead.")] + public Microsoft.Extensions.Localization.IStringLocalizer WithCulture(System.Globalization.CultureInfo culture) { throw null; } + } + public partial class ResourceManagerStringLocalizerFactory : Microsoft.Extensions.Localization.IStringLocalizerFactory + { + public ResourceManagerStringLocalizerFactory(Microsoft.Extensions.Options.IOptions localizationOptions, Microsoft.Extensions.Logging.ILoggerFactory loggerFactory) { } + public Microsoft.Extensions.Localization.IStringLocalizer Create(string baseName, string location) { throw null; } + public Microsoft.Extensions.Localization.IStringLocalizer Create(System.Type resourceSource) { throw null; } + protected virtual Microsoft.Extensions.Localization.ResourceManagerStringLocalizer CreateResourceManagerStringLocalizer(System.Reflection.Assembly assembly, string baseName) { throw null; } + protected virtual Microsoft.Extensions.Localization.ResourceLocationAttribute GetResourceLocationAttribute(System.Reflection.Assembly assembly) { throw null; } + protected virtual string GetResourcePrefix(System.Reflection.TypeInfo typeInfo) { throw null; } + protected virtual string GetResourcePrefix(System.Reflection.TypeInfo typeInfo, string baseNamespace, string resourcesRelativePath) { throw null; } + protected virtual string GetResourcePrefix(string baseResourceName, string baseNamespace) { throw null; } + protected virtual string GetResourcePrefix(string location, string baseName, string resourceLocation) { throw null; } + protected virtual Microsoft.Extensions.Localization.RootNamespaceAttribute GetRootNamespaceAttribute(System.Reflection.Assembly assembly) { throw null; } + } + [System.ObsoleteAttribute("This method is obsolete. Use `CurrentCulture` and `CurrentUICulture` instead.")] + public partial class ResourceManagerWithCultureStringLocalizer : Microsoft.Extensions.Localization.ResourceManagerStringLocalizer + { + public ResourceManagerWithCultureStringLocalizer(System.Resources.ResourceManager resourceManager, System.Reflection.Assembly resourceAssembly, string baseName, Microsoft.Extensions.Localization.IResourceNamesCache resourceNamesCache, System.Globalization.CultureInfo culture, Microsoft.Extensions.Logging.ILogger logger) : base (default(System.Resources.ResourceManager), default(System.Reflection.Assembly), default(string), default(Microsoft.Extensions.Localization.IResourceNamesCache), default(Microsoft.Extensions.Logging.ILogger)) { } + public override Microsoft.Extensions.Localization.LocalizedString this[string name] { get { throw null; } } + public override Microsoft.Extensions.Localization.LocalizedString this[string name, params object[] arguments] { get { throw null; } } + public override System.Collections.Generic.IEnumerable GetAllStrings(bool includeParentCultures) { throw null; } + } + public partial class ResourceNamesCache : Microsoft.Extensions.Localization.IResourceNamesCache + { + public ResourceNamesCache() { } + public System.Collections.Generic.IList GetOrAdd(string name, System.Func> valueFactory) { throw null; } + } + [System.AttributeUsageAttribute(System.AttributeTargets.Assembly, AllowMultiple=false, Inherited=false)] + public partial class RootNamespaceAttribute : System.Attribute + { + public RootNamespaceAttribute(string rootNamespace) { } + public string RootNamespace { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } } + } +} +namespace Microsoft.Extensions.Localization.Internal +{ + public partial class AssemblyWrapper + { + public AssemblyWrapper(System.Reflection.Assembly assembly) { } + public System.Reflection.Assembly Assembly { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } } + public virtual string FullName { get { throw null; } } + public virtual System.IO.Stream GetManifestResourceStream(string name) { throw null; } + } + public partial interface IResourceStringProvider + { + System.Collections.Generic.IList GetAllResourceStrings(System.Globalization.CultureInfo culture, bool throwOnMissing); + } + public partial class ResourceManagerStringProvider : Microsoft.Extensions.Localization.Internal.IResourceStringProvider + { + public ResourceManagerStringProvider(Microsoft.Extensions.Localization.IResourceNamesCache resourceCache, System.Resources.ResourceManager resourceManager, System.Reflection.Assembly assembly, string baseName) { } + public System.Collections.Generic.IList GetAllResourceStrings(System.Globalization.CultureInfo culture, bool throwOnMissing) { throw null; } + } +} diff --git a/src/Localization/Localization/src/Microsoft.Extensions.Localization.csproj b/src/Localization/Localization/src/Microsoft.Extensions.Localization.csproj index 5949cc5842..86ebaf1970 100644 --- a/src/Localization/Localization/src/Microsoft.Extensions.Localization.csproj +++ b/src/Localization/Localization/src/Microsoft.Extensions.Localization.csproj @@ -3,7 +3,8 @@ Microsoft .NET Extensions Application localization services and default implementation based on ResourceManager to load localized assembly resources. - netstandard2.0 + netstandard2.0;netcoreapp3.0 + netcoreapp3.0 $(NoWarn);CS1591 true localization diff --git a/src/WebEncoders/ref/Microsoft.Extensions.WebEncoders.csproj b/src/WebEncoders/ref/Microsoft.Extensions.WebEncoders.csproj index 9dea660cb0..628691a220 100644 --- a/src/WebEncoders/ref/Microsoft.Extensions.WebEncoders.csproj +++ b/src/WebEncoders/ref/Microsoft.Extensions.WebEncoders.csproj @@ -1,7 +1,7 @@ - netstandard2.0 + netstandard2.0;netcoreapp3.0 @@ -9,4 +9,9 @@ + + + + + diff --git a/src/WebEncoders/ref/Microsoft.Extensions.WebEncoders.netcoreapp3.0.cs b/src/WebEncoders/ref/Microsoft.Extensions.WebEncoders.netcoreapp3.0.cs new file mode 100644 index 0000000000..18cdcbdfa3 --- /dev/null +++ b/src/WebEncoders/ref/Microsoft.Extensions.WebEncoders.netcoreapp3.0.cs @@ -0,0 +1,55 @@ +// 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. + +namespace Microsoft.Extensions.DependencyInjection +{ + public static partial class EncoderServiceCollectionExtensions + { + public static Microsoft.Extensions.DependencyInjection.IServiceCollection AddWebEncoders(this Microsoft.Extensions.DependencyInjection.IServiceCollection services) { throw null; } + public static Microsoft.Extensions.DependencyInjection.IServiceCollection AddWebEncoders(this Microsoft.Extensions.DependencyInjection.IServiceCollection services, System.Action setupAction) { throw null; } + } +} +namespace Microsoft.Extensions.WebEncoders +{ + public sealed partial class WebEncoderOptions + { + public WebEncoderOptions() { } + public System.Text.Encodings.Web.TextEncoderSettings TextEncoderSettings { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } + } +} +namespace Microsoft.Extensions.WebEncoders.Testing +{ + public sealed partial class HtmlTestEncoder : System.Text.Encodings.Web.HtmlEncoder + { + public HtmlTestEncoder() { } + public override int MaxOutputCharactersPerInputCharacter { get { throw null; } } + public override void Encode(System.IO.TextWriter output, char[] value, int startIndex, int characterCount) { } + public override void Encode(System.IO.TextWriter output, string value, int startIndex, int characterCount) { } + public override string Encode(string value) { throw null; } + public unsafe override int FindFirstCharacterToEncode(char* text, int textLength) { throw null; } + public unsafe override bool TryEncodeUnicodeScalar(int unicodeScalar, char* buffer, int bufferLength, out int numberOfCharactersWritten) { throw null; } + public override bool WillEncode(int unicodeScalar) { throw null; } + } + public partial class JavaScriptTestEncoder : System.Text.Encodings.Web.JavaScriptEncoder + { + public JavaScriptTestEncoder() { } + public override int MaxOutputCharactersPerInputCharacter { get { throw null; } } + public override void Encode(System.IO.TextWriter output, char[] value, int startIndex, int characterCount) { } + public override void Encode(System.IO.TextWriter output, string value, int startIndex, int characterCount) { } + public override string Encode(string value) { throw null; } + public unsafe override int FindFirstCharacterToEncode(char* text, int textLength) { throw null; } + public unsafe override bool TryEncodeUnicodeScalar(int unicodeScalar, char* buffer, int bufferLength, out int numberOfCharactersWritten) { throw null; } + public override bool WillEncode(int unicodeScalar) { throw null; } + } + public partial class UrlTestEncoder : System.Text.Encodings.Web.UrlEncoder + { + public UrlTestEncoder() { } + public override int MaxOutputCharactersPerInputCharacter { get { throw null; } } + public override void Encode(System.IO.TextWriter output, char[] value, int startIndex, int characterCount) { } + public override void Encode(System.IO.TextWriter output, string value, int startIndex, int characterCount) { } + public override string Encode(string value) { throw null; } + public unsafe override int FindFirstCharacterToEncode(char* text, int textLength) { throw null; } + public unsafe override bool TryEncodeUnicodeScalar(int unicodeScalar, char* buffer, int bufferLength, out int numberOfCharactersWritten) { throw null; } + public override bool WillEncode(int unicodeScalar) { throw null; } + } +} diff --git a/src/WebEncoders/src/Microsoft.Extensions.WebEncoders.csproj b/src/WebEncoders/src/Microsoft.Extensions.WebEncoders.csproj index cec4a31d86..2ed725eac7 100644 --- a/src/WebEncoders/src/Microsoft.Extensions.WebEncoders.csproj +++ b/src/WebEncoders/src/Microsoft.Extensions.WebEncoders.csproj @@ -2,7 +2,8 @@ Contains registration and configuration APIs to add the core framework encoders to a dependency injection container. - netstandard2.0 + netstandard2.0;netcoreapp3.0 + netcoreapp3.0 $(NoWarn);CS1591 true aspnetcore @@ -13,6 +14,9 @@ + + + From 42eec2cbfa9d8a86c110b7384a9cfbba8a5b0641 Mon Sep 17 00:00:00 2001 From: John Luo Date: Wed, 7 Aug 2019 15:44:03 -0700 Subject: [PATCH 0146/1101] Fix build break by using API that's available across net472 and netcoreapp3.0 (dotnet/extensions#2157) \n\nCommit migrated from https://github.com/dotnet/extensions/commit/6e96af8eba3bba8f4a8e02e38dd3ac9549f85d34 --- .../Microsoft.JSInterop/test/DotNetDispatcherTest.cs | 8 ++++---- .../Microsoft.JSInterop/test/JSRuntimeBaseTest.cs | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/JSInterop/Microsoft.JSInterop/test/DotNetDispatcherTest.cs b/src/JSInterop/Microsoft.JSInterop/test/DotNetDispatcherTest.cs index 8e9d0dc3b9..282aa0f364 100644 --- a/src/JSInterop/Microsoft.JSInterop/test/DotNetDispatcherTest.cs +++ b/src/JSInterop/Microsoft.JSInterop/test/DotNetDispatcherTest.cs @@ -250,7 +250,7 @@ namespace Microsoft.JSInterop DotNetDispatcher.EndInvoke(argsJson); // Assert - Assert.True(task.IsCompletedSuccessfully); + Assert.True(task.IsCompleted && task.Status == TaskStatus.RanToCompletion); var result = task.Result; Assert.Equal(testDTO.StringVal, result.StringVal); Assert.Equal(testDTO.IntVal, result.IntVal); @@ -597,7 +597,7 @@ namespace Microsoft.JSInterop DotNetDispatcher.ParseEndInvokeArguments(jsRuntime, $"[{jsRuntime.LastInvocationAsyncHandle}, true, {{\"intVal\": 7}}]"); - Assert.True(task.IsCompletedSuccessfully); + Assert.True(task.IsCompleted && task.Status == TaskStatus.RanToCompletion); Assert.Equal(7, task.Result.IntVal); } @@ -609,7 +609,7 @@ namespace Microsoft.JSInterop DotNetDispatcher.ParseEndInvokeArguments(jsRuntime, $"[{jsRuntime.LastInvocationAsyncHandle}, true, [1, 2, 3]]"); - Assert.True(task.IsCompletedSuccessfully); + Assert.True(task.IsCompleted && task.Status == TaskStatus.RanToCompletion); Assert.Equal(new[] { 1, 2, 3 }, task.Result); } @@ -621,7 +621,7 @@ namespace Microsoft.JSInterop DotNetDispatcher.ParseEndInvokeArguments(jsRuntime, $"[{jsRuntime.LastInvocationAsyncHandle}, true, null]"); - Assert.True(task.IsCompletedSuccessfully); + Assert.True(task.IsCompleted && task.Status == TaskStatus.RanToCompletion); Assert.Null(task.Result); } diff --git a/src/JSInterop/Microsoft.JSInterop/test/JSRuntimeBaseTest.cs b/src/JSInterop/Microsoft.JSInterop/test/JSRuntimeBaseTest.cs index 468f8efba3..2714886f9a 100644 --- a/src/JSInterop/Microsoft.JSInterop/test/JSRuntimeBaseTest.cs +++ b/src/JSInterop/Microsoft.JSInterop/test/JSRuntimeBaseTest.cs @@ -66,7 +66,7 @@ namespace Microsoft.JSInterop runtime.EndInvokeJS(2, succeeded: true, ref reader); - Assert.True(task.IsCompletedSuccessfully); + Assert.True(task.IsCompleted && task.Status == TaskStatus.RanToCompletion); } [Fact] From 4c36eb2601475baf70609d6b13c78f199f5e312f Mon Sep 17 00:00:00 2001 From: "dotnet-maestro[bot]" <42748379+dotnet-maestro[bot]@users.noreply.github.com> Date: Thu, 8 Aug 2019 14:38:57 +0000 Subject: [PATCH 0147/1101] Update dependencies from https://github.com/aspnet/AspNetCore-Tooling build 20190807.1 (#12971) - Microsoft.NET.Sdk.Razor - 5.0.0-alpha1.19407.1 - Microsoft.CodeAnalysis.Razor - 5.0.0-alpha1.19407.1 - Microsoft.AspNetCore.Razor.Language - 5.0.0-alpha1.19407.1 - Microsoft.AspNetCore.Mvc.Razor.Extensions - 5.0.0-alpha1.19407.1 --- eng/Version.Details.xml | 16 ++++++++-------- eng/Versions.props | 8 ++++---- 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/eng/Version.Details.xml b/eng/Version.Details.xml index 67294ca344..15dfbd1a13 100644 --- a/eng/Version.Details.xml +++ b/eng/Version.Details.xml @@ -13,21 +13,21 @@ https://github.com/aspnet/Blazor b2c48dd8c9099f71908fac26089cbea2c76d06a1 - + https://github.com/aspnet/AspNetCore-Tooling - 2e70e0cbd7d8b952313d1114cd750befca2b1453 + 448a88e86d20fd9315901f663318d64c9c6841bf - + https://github.com/aspnet/AspNetCore-Tooling - 2e70e0cbd7d8b952313d1114cd750befca2b1453 + 448a88e86d20fd9315901f663318d64c9c6841bf - + https://github.com/aspnet/AspNetCore-Tooling - 2e70e0cbd7d8b952313d1114cd750befca2b1453 + 448a88e86d20fd9315901f663318d64c9c6841bf - + https://github.com/aspnet/AspNetCore-Tooling - 2e70e0cbd7d8b952313d1114cd750befca2b1453 + 448a88e86d20fd9315901f663318d64c9c6841bf https://github.com/aspnet/EntityFrameworkCore diff --git a/eng/Versions.props b/eng/Versions.props index 3ec1e39ae3..3fcb044a1f 100644 --- a/eng/Versions.props +++ b/eng/Versions.props @@ -162,10 +162,10 @@ 3.0.0-preview9.19402.9 3.0.0-preview9.19402.9 - 5.0.0-alpha1.19406.4 - 5.0.0-alpha1.19406.4 - 5.0.0-alpha1.19406.4 - 5.0.0-alpha1.19406.4 + 5.0.0-alpha1.19407.1 + 5.0.0-alpha1.19407.1 + 5.0.0-alpha1.19407.1 + 5.0.0-alpha1.19407.1 -<<<<<<< HEAD 5.0.0-alpha1.19407.1 5.0.0-alpha1.19407.1 5.0.0-alpha1.19407.1 5.0.0-alpha1.19407.1 -======= - 3.0.0-preview9.19405.6 - 3.0.0-preview9.19405.6 - 3.0.0-preview9.19405.6 - 3.0.0-preview9.19405.6 ->>>>>>> release/3.0 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + ValFromResource + + \ No newline at end of file diff --git a/src/Localization/Localization/test/Microsoft.Extensions.Localization.RootNamespace.Tests/StringLocalizerOfTRootNamespaceTest.cs b/src/Localization/Localization/test/Microsoft.Extensions.Localization.RootNamespace.Tests/StringLocalizerOfTRootNamespaceTest.cs new file mode 100644 index 0000000000..7c892e65dd --- /dev/null +++ b/src/Localization/Localization/test/Microsoft.Extensions.Localization.RootNamespace.Tests/StringLocalizerOfTRootNamespaceTest.cs @@ -0,0 +1,26 @@ +// 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 LocalizationTest.Abc.Controllers; +using Microsoft.Extensions.Logging.Abstractions; +using Microsoft.Extensions.Options; +using Moq; +using Xunit; + +namespace Microsoft.Extensions.Localization.RootNamespace.Tests +{ + public class StringLocalizerOfTRootNamespaceTest + { + [Fact] + public void RootNamespace() + { + var locOptions = new LocalizationOptions(); + var options = new Mock>(); + options.Setup(o => o.Value).Returns(locOptions); + var factory = new ResourceManagerStringLocalizerFactory(options.Object, NullLoggerFactory.Instance); + + var valuesLoc = factory.Create(typeof(ValuesController)); + Assert.Equal("ValFromResource", valuesLoc["String1"]); + } + } +} diff --git a/src/Localization/Localization/test/LocalizationServiceCollectionExtensionsTest.cs b/src/Localization/Localization/test/Microsoft.Extensions.Localization.Tests/LocalizationServiceCollectionExtensionsTest.cs similarity index 100% rename from src/Localization/Localization/test/LocalizationServiceCollectionExtensionsTest.cs rename to src/Localization/Localization/test/Microsoft.Extensions.Localization.Tests/LocalizationServiceCollectionExtensionsTest.cs diff --git a/src/Localization/Localization/test/Microsoft.Extensions.Localization.Tests.csproj b/src/Localization/Localization/test/Microsoft.Extensions.Localization.Tests/Microsoft.Extensions.Localization.Tests.csproj similarity index 100% rename from src/Localization/Localization/test/Microsoft.Extensions.Localization.Tests.csproj rename to src/Localization/Localization/test/Microsoft.Extensions.Localization.Tests/Microsoft.Extensions.Localization.Tests.csproj diff --git a/src/Localization/Localization/test/Microsoft.Extensions.Localization.Tests/Model.cs b/src/Localization/Localization/test/Microsoft.Extensions.Localization.Tests/Model.cs new file mode 100644 index 0000000000..9d95c370fd --- /dev/null +++ b/src/Localization/Localization/test/Microsoft.Extensions.Localization.Tests/Model.cs @@ -0,0 +1,8 @@ +// This namespace for test resources with alternative RootNamespace +namespace MyNamespace +{ + public class Model + { + + } +} \ No newline at end of file diff --git a/src/Localization/Localization/test/ResourceManagerStringLocalizerFactoryTest.cs b/src/Localization/Localization/test/Microsoft.Extensions.Localization.Tests/ResourceManagerStringLocalizerFactoryTest.cs similarity index 96% rename from src/Localization/Localization/test/ResourceManagerStringLocalizerFactoryTest.cs rename to src/Localization/Localization/test/Microsoft.Extensions.Localization.Tests/ResourceManagerStringLocalizerFactoryTest.cs index 7a18c0e4bd..86f6e15ccd 100644 --- a/src/Localization/Localization/test/ResourceManagerStringLocalizerFactoryTest.cs +++ b/src/Localization/Localization/test/Microsoft.Extensions.Localization.Tests/ResourceManagerStringLocalizerFactoryTest.cs @@ -7,6 +7,7 @@ using System.Reflection; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging.Abstractions; using Microsoft.Extensions.Options; +using MyNamespace; using Moq; using Xunit; @@ -132,7 +133,7 @@ namespace Microsoft.Extensions.Localization.Tests var loggerFactory = NullLoggerFactory.Instance; var resourcePath = Path.Combine("My", "Resources"); - var rootNamespace = "MyNamespace"; + var rootNamespace = nameof(MyNamespace); var rootNamespaceAttribute = new RootNamespaceAttribute(rootNamespace); var typeFactory = new TestResourceManagerStringLocalizerFactory( @@ -141,12 +142,13 @@ namespace Microsoft.Extensions.Localization.Tests rootNamespaceAttribute: rootNamespaceAttribute, loggerFactory: loggerFactory); - var type = typeof(ResourceManagerStringLocalizerFactoryTest); + var type = typeof(Model); + // Act typeFactory.Create(type); // Assert - Assert.Equal($"Microsoft.Extensions.Localization.Tests.ResourceManagerStringLocalizerFactoryTest", typeFactory.BaseName); + Assert.Equal($"{rootNamespace}.{nameof(Model)}", typeFactory.BaseName); } [Fact] @@ -159,7 +161,7 @@ namespace Microsoft.Extensions.Localization.Tests var loggerFactory = NullLoggerFactory.Instance; var resourcePath = Path.Combine("My", "Resources"); - var rootNamespace = "MyNamespace"; + var rootNamespace = nameof(MyNamespace); var resourceLocationAttribute = new ResourceLocationAttribute(resourcePath); var rootNamespaceAttribute = new RootNamespaceAttribute(rootNamespace); @@ -169,12 +171,13 @@ namespace Microsoft.Extensions.Localization.Tests rootNamespaceAttribute, loggerFactory); - var type = typeof(ResourceManagerStringLocalizerFactoryTest); + var type = typeof(Model); + // Act typeFactory.Create(type); // Assert - Assert.Equal($"MyNamespace.My.Resources.ResourceManagerStringLocalizerFactoryTest", typeFactory.BaseName); + Assert.Equal($"{rootNamespace}.My.Resources.{nameof(Model)}", typeFactory.BaseName); } [Fact] diff --git a/src/Localization/Localization/test/ResourceManagerStringLocalizerTest.cs b/src/Localization/Localization/test/Microsoft.Extensions.Localization.Tests/ResourceManagerStringLocalizerTest.cs similarity index 100% rename from src/Localization/Localization/test/ResourceManagerStringLocalizerTest.cs rename to src/Localization/Localization/test/Microsoft.Extensions.Localization.Tests/ResourceManagerStringLocalizerTest.cs diff --git a/src/Localization/Localization/test/StringLocalizerOfTTest.cs b/src/Localization/Localization/test/Microsoft.Extensions.Localization.Tests/StringLocalizerOfTTest.cs similarity index 100% rename from src/Localization/Localization/test/StringLocalizerOfTTest.cs rename to src/Localization/Localization/test/Microsoft.Extensions.Localization.Tests/StringLocalizerOfTTest.cs From c53e177ea369a07d76bd0d9e1d4496879a84f33a Mon Sep 17 00:00:00 2001 From: Isaac Levin <8878502+isaac2004@users.noreply.github.com> Date: Fri, 9 Aug 2019 12:01:33 -0400 Subject: [PATCH 0151/1101] Update SpaTemplateTestBase.cs --- .../test/SpaTemplateTest/SpaTemplateTestBase.cs | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/src/ProjectTemplates/test/SpaTemplateTest/SpaTemplateTestBase.cs b/src/ProjectTemplates/test/SpaTemplateTest/SpaTemplateTestBase.cs index 95d84c965c..f85b5c20b3 100644 --- a/src/ProjectTemplates/test/SpaTemplateTest/SpaTemplateTestBase.cs +++ b/src/ProjectTemplates/test/SpaTemplateTest/SpaTemplateTestBase.cs @@ -66,11 +66,8 @@ namespace Templates.Test.SpaTemplateTest var lintResult = await ProcessEx.RunViaShellAsync(Output, clientAppSubdirPath, "npm run lint"); Assert.True(0 == lintResult.ExitCode, ErrorMessages.GetFailedProcessMessage("npm run lint", Project, lintResult)); - if (template == "react" || template == "reactredux") - { - var testResult = await ProcessEx.RunViaShellAsync(Output, clientAppSubdirPath, "npm run test"); - Assert.True(0 == testResult.ExitCode, ErrorMessages.GetFailedProcessMessage("npm run test", Project, testResult)); - } + var testResult = await ProcessEx.RunViaShellAsync(Output, clientAppSubdirPath, "npm run test"); + Assert.True(0 == testResult.ExitCode, ErrorMessages.GetFailedProcessMessage("npm run test", Project, testResult)); var publishResult = await Project.RunDotNetPublishAsync(); Assert.True(0 == publishResult.ExitCode, ErrorMessages.GetFailedProcessMessage("publish", Project, publishResult)); From 6c66664ae688f4a7b5c3124cfc4a0e8c6646b8ae Mon Sep 17 00:00:00 2001 From: John Luo Date: Fri, 9 Aug 2019 14:57:35 -0700 Subject: [PATCH 0152/1101] Update tfm in master to netcoreap5.0 \n\nCommit migrated from https://github.com/dotnet/extensions/commit/3fa337f230eda6dc8b94a65e65343d777b294011 --- .../src/Microsoft.Extensions.Configuration.KeyPerFile.csproj | 4 ++-- ...ft.Extensions.Diagnostics.HealthChecks.Abstractions.csproj | 4 ++-- .../src/Microsoft.Extensions.Diagnostics.HealthChecks.csproj | 4 ++-- .../Microsoft.JSInterop/src/Microsoft.JSInterop.csproj | 4 ++-- .../src/Microsoft.Extensions.Localization.Abstractions.csproj | 4 ++-- .../Localization/src/Microsoft.Extensions.Localization.csproj | 4 ++-- src/WebEncoders/src/Microsoft.Extensions.WebEncoders.csproj | 4 ++-- 7 files changed, 14 insertions(+), 14 deletions(-) diff --git a/src/Configuration.KeyPerFile/src/Microsoft.Extensions.Configuration.KeyPerFile.csproj b/src/Configuration.KeyPerFile/src/Microsoft.Extensions.Configuration.KeyPerFile.csproj index 10f41fa9f4..77664416f9 100644 --- a/src/Configuration.KeyPerFile/src/Microsoft.Extensions.Configuration.KeyPerFile.csproj +++ b/src/Configuration.KeyPerFile/src/Microsoft.Extensions.Configuration.KeyPerFile.csproj @@ -2,8 +2,8 @@ Configuration provider that uses files in a directory for Microsoft.Extensions.Configuration. - netstandard2.0;netcoreapp3.0 - netcoreapp3.0 + netstandard2.0;netcoreapp5.0 + netcoreapp5.0 true true diff --git a/src/HealthChecks/Abstractions/src/Microsoft.Extensions.Diagnostics.HealthChecks.Abstractions.csproj b/src/HealthChecks/Abstractions/src/Microsoft.Extensions.Diagnostics.HealthChecks.Abstractions.csproj index f41b257c63..e5cf8c251a 100644 --- a/src/HealthChecks/Abstractions/src/Microsoft.Extensions.Diagnostics.HealthChecks.Abstractions.csproj +++ b/src/HealthChecks/Abstractions/src/Microsoft.Extensions.Diagnostics.HealthChecks.Abstractions.csproj @@ -7,8 +7,8 @@ Commonly Used Types Microsoft.Extensions.Diagnostics.HealthChecks.IHealthCheck Microsoft.Extensions.Diagnostics.HealthChecks - netstandard2.0;netcoreapp3.0 - netcoreapp3.0 + netstandard2.0;netcoreapp5.0 + netcoreapp5.0 $(NoWarn);CS1591 true diagnostics;healthchecks diff --git a/src/HealthChecks/HealthChecks/src/Microsoft.Extensions.Diagnostics.HealthChecks.csproj b/src/HealthChecks/HealthChecks/src/Microsoft.Extensions.Diagnostics.HealthChecks.csproj index 4005685011..d3b3f82b0d 100644 --- a/src/HealthChecks/HealthChecks/src/Microsoft.Extensions.Diagnostics.HealthChecks.csproj +++ b/src/HealthChecks/HealthChecks/src/Microsoft.Extensions.Diagnostics.HealthChecks.csproj @@ -6,8 +6,8 @@ Commonly Used Types: Microsoft.Extensions.Diagnostics.HealthChecks.HealthCheckService Microsoft.Extensions.Diagnostics.HealthChecks.IHealthChecksBuilder - netstandard2.0;netcoreapp3.0 - netcoreapp3.0 + netstandard2.0;netcoreapp5.0 + netcoreapp5.0 $(NoWarn);CS1591 true diagnostics;healthchecks diff --git a/src/JSInterop/Microsoft.JSInterop/src/Microsoft.JSInterop.csproj b/src/JSInterop/Microsoft.JSInterop/src/Microsoft.JSInterop.csproj index 044c843b28..3021d18cc3 100644 --- a/src/JSInterop/Microsoft.JSInterop/src/Microsoft.JSInterop.csproj +++ b/src/JSInterop/Microsoft.JSInterop/src/Microsoft.JSInterop.csproj @@ -1,8 +1,8 @@  - netstandard2.0;netcoreapp3.0 - netcoreapp3.0 + netstandard2.0;netcoreapp5.0 + netcoreapp5.0 Abstractions and features for interop between .NET and JavaScript code. javascript;interop true diff --git a/src/Localization/Abstractions/src/Microsoft.Extensions.Localization.Abstractions.csproj b/src/Localization/Abstractions/src/Microsoft.Extensions.Localization.Abstractions.csproj index 09b8bf65b7..919d4f3d28 100644 --- a/src/Localization/Abstractions/src/Microsoft.Extensions.Localization.Abstractions.csproj +++ b/src/Localization/Abstractions/src/Microsoft.Extensions.Localization.Abstractions.csproj @@ -6,8 +6,8 @@ Commonly used types: Microsoft.Extensions.Localization.IStringLocalizer Microsoft.Extensions.Localization.IStringLocalizer<T> - netstandard2.0;netcoreapp3.0 - netcoreapp3.0 + netstandard2.0;netcoreapp5.0 + netcoreapp5.0 $(NoWarn);CS1591 true localization diff --git a/src/Localization/Localization/src/Microsoft.Extensions.Localization.csproj b/src/Localization/Localization/src/Microsoft.Extensions.Localization.csproj index 86ebaf1970..1cfdc34dfb 100644 --- a/src/Localization/Localization/src/Microsoft.Extensions.Localization.csproj +++ b/src/Localization/Localization/src/Microsoft.Extensions.Localization.csproj @@ -3,8 +3,8 @@ Microsoft .NET Extensions Application localization services and default implementation based on ResourceManager to load localized assembly resources. - netstandard2.0;netcoreapp3.0 - netcoreapp3.0 + netstandard2.0;netcoreapp5.0 + netcoreapp5.0 $(NoWarn);CS1591 true localization diff --git a/src/WebEncoders/src/Microsoft.Extensions.WebEncoders.csproj b/src/WebEncoders/src/Microsoft.Extensions.WebEncoders.csproj index 2ed725eac7..f4c4294413 100644 --- a/src/WebEncoders/src/Microsoft.Extensions.WebEncoders.csproj +++ b/src/WebEncoders/src/Microsoft.Extensions.WebEncoders.csproj @@ -2,8 +2,8 @@ Contains registration and configuration APIs to add the core framework encoders to a dependency injection container. - netstandard2.0;netcoreapp3.0 - netcoreapp3.0 + netstandard2.0;netcoreapp5.0 + netcoreapp5.0 $(NoWarn);CS1591 true aspnetcore From 4e01ec63483e3cd35e35eeda1b4da43ef183e596 Mon Sep 17 00:00:00 2001 From: John Luo Date: Fri, 9 Aug 2019 15:17:50 -0700 Subject: [PATCH 0153/1101] Update ref assemblies and missed updates \n\nCommit migrated from https://github.com/dotnet/extensions/commit/6fc17ec76f56d377047e386ea253445fab3581c1 --- ...Extensions.Configuration.KeyPerFile.csproj | 6 +- ....Configuration.KeyPerFile.netcoreapp5.0.cs | 29 ++++++ ...t.Extensions.FileProviders.Embedded.csproj | 6 +- ...ns.FileProviders.Embedded.netcoreapp5.0.cs | 39 ++++++++ ...t.Extensions.FileProviders.Embedded.csproj | 6 +- ....FileProviders.Embedded.multitarget.nuspec | 2 +- ...leProviders.Embedded.netcoreapp5.0.nuspec} | 2 +- ...agnostics.HealthChecks.Abstractions.csproj | 6 +- ...HealthChecks.Abstractions.netcoreapp5.0.cs | 72 ++++++++++++++ ...Extensions.Diagnostics.HealthChecks.csproj | 6 +- ....Diagnostics.HealthChecks.netcoreapp5.0.cs | 59 ++++++++++++ .../ref/Microsoft.JSInterop.csproj | 6 +- .../ref/Microsoft.JSInterop.netcoreapp5.0.cs | 64 +++++++++++++ ...xtensions.Localization.Abstractions.csproj | 6 +- ...Localization.Abstractions.netcoreapp5.0.cs | 49 ++++++++++ .../Microsoft.Extensions.Localization.csproj | 6 +- ...t.Extensions.Localization.netcoreapp5.0.cs | 93 +++++++++++++++++++ .../Microsoft.Extensions.WebEncoders.csproj | 6 +- ...ft.Extensions.WebEncoders.netcoreapp5.0.cs | 55 +++++++++++ 19 files changed, 489 insertions(+), 29 deletions(-) create mode 100644 src/Configuration.KeyPerFile/ref/Microsoft.Extensions.Configuration.KeyPerFile.netcoreapp5.0.cs create mode 100644 src/FileProviders/Embedded/ref/Microsoft.Extensions.FileProviders.Embedded.netcoreapp5.0.cs rename src/FileProviders/Embedded/src/{Microsoft.Extensions.FileProviders.Embedded.netcoreapp3.0.nuspec => Microsoft.Extensions.FileProviders.Embedded.netcoreapp5.0.nuspec} (94%) create mode 100644 src/HealthChecks/Abstractions/ref/Microsoft.Extensions.Diagnostics.HealthChecks.Abstractions.netcoreapp5.0.cs create mode 100644 src/HealthChecks/HealthChecks/ref/Microsoft.Extensions.Diagnostics.HealthChecks.netcoreapp5.0.cs create mode 100644 src/JSInterop/Microsoft.JSInterop/ref/Microsoft.JSInterop.netcoreapp5.0.cs create mode 100644 src/Localization/Abstractions/ref/Microsoft.Extensions.Localization.Abstractions.netcoreapp5.0.cs create mode 100644 src/Localization/Localization/ref/Microsoft.Extensions.Localization.netcoreapp5.0.cs create mode 100644 src/WebEncoders/ref/Microsoft.Extensions.WebEncoders.netcoreapp5.0.cs diff --git a/src/Configuration.KeyPerFile/ref/Microsoft.Extensions.Configuration.KeyPerFile.csproj b/src/Configuration.KeyPerFile/ref/Microsoft.Extensions.Configuration.KeyPerFile.csproj index 4e074ab493..2ba44d34fa 100644 --- a/src/Configuration.KeyPerFile/ref/Microsoft.Extensions.Configuration.KeyPerFile.csproj +++ b/src/Configuration.KeyPerFile/ref/Microsoft.Extensions.Configuration.KeyPerFile.csproj @@ -1,15 +1,15 @@ - netstandard2.0;netcoreapp3.0 + netstandard2.0;netcoreapp5.0 - - + + diff --git a/src/Configuration.KeyPerFile/ref/Microsoft.Extensions.Configuration.KeyPerFile.netcoreapp5.0.cs b/src/Configuration.KeyPerFile/ref/Microsoft.Extensions.Configuration.KeyPerFile.netcoreapp5.0.cs new file mode 100644 index 0000000000..e26ca1909d --- /dev/null +++ b/src/Configuration.KeyPerFile/ref/Microsoft.Extensions.Configuration.KeyPerFile.netcoreapp5.0.cs @@ -0,0 +1,29 @@ +// 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. + +namespace Microsoft.Extensions.Configuration +{ + public static partial class KeyPerFileConfigurationBuilderExtensions + { + public static Microsoft.Extensions.Configuration.IConfigurationBuilder AddKeyPerFile(this Microsoft.Extensions.Configuration.IConfigurationBuilder builder, System.Action configureSource) { throw null; } + public static Microsoft.Extensions.Configuration.IConfigurationBuilder AddKeyPerFile(this Microsoft.Extensions.Configuration.IConfigurationBuilder builder, string directoryPath, bool optional) { throw null; } + } +} +namespace Microsoft.Extensions.Configuration.KeyPerFile +{ + public partial class KeyPerFileConfigurationProvider : Microsoft.Extensions.Configuration.ConfigurationProvider + { + public KeyPerFileConfigurationProvider(Microsoft.Extensions.Configuration.KeyPerFile.KeyPerFileConfigurationSource source) { } + public override void Load() { } + public override string ToString() { throw null; } + } + public partial class KeyPerFileConfigurationSource : Microsoft.Extensions.Configuration.IConfigurationSource + { + public KeyPerFileConfigurationSource() { } + public Microsoft.Extensions.FileProviders.IFileProvider FileProvider { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } + public System.Func IgnoreCondition { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } + public string IgnorePrefix { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } + public bool Optional { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } + public Microsoft.Extensions.Configuration.IConfigurationProvider Build(Microsoft.Extensions.Configuration.IConfigurationBuilder builder) { throw null; } + } +} diff --git a/src/FileProviders/Embedded/ref/Microsoft.Extensions.FileProviders.Embedded.csproj b/src/FileProviders/Embedded/ref/Microsoft.Extensions.FileProviders.Embedded.csproj index e9407f9164..98d071201c 100644 --- a/src/FileProviders/Embedded/ref/Microsoft.Extensions.FileProviders.Embedded.csproj +++ b/src/FileProviders/Embedded/ref/Microsoft.Extensions.FileProviders.Embedded.csproj @@ -1,14 +1,14 @@ - netstandard2.0;netcoreapp3.0 + netstandard2.0;netcoreapp5.0 - - + + diff --git a/src/FileProviders/Embedded/ref/Microsoft.Extensions.FileProviders.Embedded.netcoreapp5.0.cs b/src/FileProviders/Embedded/ref/Microsoft.Extensions.FileProviders.Embedded.netcoreapp5.0.cs new file mode 100644 index 0000000000..1596f191fd --- /dev/null +++ b/src/FileProviders/Embedded/ref/Microsoft.Extensions.FileProviders.Embedded.netcoreapp5.0.cs @@ -0,0 +1,39 @@ +// 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. + +namespace Microsoft.Extensions.FileProviders +{ + public partial class EmbeddedFileProvider : Microsoft.Extensions.FileProviders.IFileProvider + { + public EmbeddedFileProvider(System.Reflection.Assembly assembly) { } + public EmbeddedFileProvider(System.Reflection.Assembly assembly, string baseNamespace) { } + public Microsoft.Extensions.FileProviders.IDirectoryContents GetDirectoryContents(string subpath) { throw null; } + public Microsoft.Extensions.FileProviders.IFileInfo GetFileInfo(string subpath) { throw null; } + public Microsoft.Extensions.Primitives.IChangeToken Watch(string pattern) { throw null; } + } + public partial class ManifestEmbeddedFileProvider : Microsoft.Extensions.FileProviders.IFileProvider + { + public ManifestEmbeddedFileProvider(System.Reflection.Assembly assembly) { } + public ManifestEmbeddedFileProvider(System.Reflection.Assembly assembly, string root) { } + public ManifestEmbeddedFileProvider(System.Reflection.Assembly assembly, string root, System.DateTimeOffset lastModified) { } + public ManifestEmbeddedFileProvider(System.Reflection.Assembly assembly, string root, string manifestName, System.DateTimeOffset lastModified) { } + public System.Reflection.Assembly Assembly { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } } + public Microsoft.Extensions.FileProviders.IDirectoryContents GetDirectoryContents(string subpath) { throw null; } + public Microsoft.Extensions.FileProviders.IFileInfo GetFileInfo(string subpath) { throw null; } + public Microsoft.Extensions.Primitives.IChangeToken Watch(string filter) { throw null; } + } +} +namespace Microsoft.Extensions.FileProviders.Embedded +{ + public partial class EmbeddedResourceFileInfo : Microsoft.Extensions.FileProviders.IFileInfo + { + public EmbeddedResourceFileInfo(System.Reflection.Assembly assembly, string resourcePath, string name, System.DateTimeOffset lastModified) { } + public bool Exists { get { throw null; } } + public bool IsDirectory { get { throw null; } } + public System.DateTimeOffset LastModified { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } } + public long Length { get { throw null; } } + public string Name { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } } + public string PhysicalPath { get { throw null; } } + public System.IO.Stream CreateReadStream() { throw null; } + } +} diff --git a/src/FileProviders/Embedded/src/Microsoft.Extensions.FileProviders.Embedded.csproj b/src/FileProviders/Embedded/src/Microsoft.Extensions.FileProviders.Embedded.csproj index 115d9affe1..3c99563949 100644 --- a/src/FileProviders/Embedded/src/Microsoft.Extensions.FileProviders.Embedded.csproj +++ b/src/FileProviders/Embedded/src/Microsoft.Extensions.FileProviders.Embedded.csproj @@ -3,10 +3,10 @@ Microsoft.Extensions.FileProviders File provider for files in embedded resources for Microsoft.Extensions.FileProviders. - netstandard2.0;netcoreapp3.0 + netstandard2.0;netcoreapp5.0 $(MSBuildProjectName).multitarget.nuspec - netcoreapp3.0 - $(MSBuildProjectName).netcoreapp3.0.nuspec + netcoreapp5.0 + $(MSBuildProjectName).netcoreapp5.0.nuspec true true diff --git a/src/FileProviders/Embedded/src/Microsoft.Extensions.FileProviders.Embedded.multitarget.nuspec b/src/FileProviders/Embedded/src/Microsoft.Extensions.FileProviders.Embedded.multitarget.nuspec index 874c90c79d..3d693569e4 100644 --- a/src/FileProviders/Embedded/src/Microsoft.Extensions.FileProviders.Embedded.multitarget.nuspec +++ b/src/FileProviders/Embedded/src/Microsoft.Extensions.FileProviders.Embedded.multitarget.nuspec @@ -3,7 +3,7 @@ $CommonMetadataElements$ - + diff --git a/src/FileProviders/Embedded/src/Microsoft.Extensions.FileProviders.Embedded.netcoreapp3.0.nuspec b/src/FileProviders/Embedded/src/Microsoft.Extensions.FileProviders.Embedded.netcoreapp5.0.nuspec similarity index 94% rename from src/FileProviders/Embedded/src/Microsoft.Extensions.FileProviders.Embedded.netcoreapp3.0.nuspec rename to src/FileProviders/Embedded/src/Microsoft.Extensions.FileProviders.Embedded.netcoreapp5.0.nuspec index f98f0b4ad5..4bc62a0e6b 100644 --- a/src/FileProviders/Embedded/src/Microsoft.Extensions.FileProviders.Embedded.netcoreapp3.0.nuspec +++ b/src/FileProviders/Embedded/src/Microsoft.Extensions.FileProviders.Embedded.netcoreapp5.0.nuspec @@ -3,7 +3,7 @@ $CommonMetadataElements$ - + diff --git a/src/HealthChecks/Abstractions/ref/Microsoft.Extensions.Diagnostics.HealthChecks.Abstractions.csproj b/src/HealthChecks/Abstractions/ref/Microsoft.Extensions.Diagnostics.HealthChecks.Abstractions.csproj index f7514489fa..d202fe2d71 100644 --- a/src/HealthChecks/Abstractions/ref/Microsoft.Extensions.Diagnostics.HealthChecks.Abstractions.csproj +++ b/src/HealthChecks/Abstractions/ref/Microsoft.Extensions.Diagnostics.HealthChecks.Abstractions.csproj @@ -1,14 +1,14 @@ - netstandard2.0;netcoreapp3.0 + netstandard2.0;netcoreapp5.0 - - + + diff --git a/src/HealthChecks/Abstractions/ref/Microsoft.Extensions.Diagnostics.HealthChecks.Abstractions.netcoreapp5.0.cs b/src/HealthChecks/Abstractions/ref/Microsoft.Extensions.Diagnostics.HealthChecks.Abstractions.netcoreapp5.0.cs new file mode 100644 index 0000000000..8c53adc275 --- /dev/null +++ b/src/HealthChecks/Abstractions/ref/Microsoft.Extensions.Diagnostics.HealthChecks.Abstractions.netcoreapp5.0.cs @@ -0,0 +1,72 @@ +// 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. + +namespace Microsoft.Extensions.Diagnostics.HealthChecks +{ + public sealed partial class HealthCheckContext + { + public HealthCheckContext() { } + public Microsoft.Extensions.Diagnostics.HealthChecks.HealthCheckRegistration Registration { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } + } + public sealed partial class HealthCheckRegistration + { + public HealthCheckRegistration(string name, Microsoft.Extensions.Diagnostics.HealthChecks.IHealthCheck instance, Microsoft.Extensions.Diagnostics.HealthChecks.HealthStatus? failureStatus, System.Collections.Generic.IEnumerable tags) { } + public HealthCheckRegistration(string name, Microsoft.Extensions.Diagnostics.HealthChecks.IHealthCheck instance, Microsoft.Extensions.Diagnostics.HealthChecks.HealthStatus? failureStatus, System.Collections.Generic.IEnumerable tags, System.TimeSpan? timeout) { } + public HealthCheckRegistration(string name, System.Func factory, Microsoft.Extensions.Diagnostics.HealthChecks.HealthStatus? failureStatus, System.Collections.Generic.IEnumerable tags) { } + public HealthCheckRegistration(string name, System.Func factory, Microsoft.Extensions.Diagnostics.HealthChecks.HealthStatus? failureStatus, System.Collections.Generic.IEnumerable tags, System.TimeSpan? timeout) { } + public System.Func Factory { get { throw null; } set { } } + public Microsoft.Extensions.Diagnostics.HealthChecks.HealthStatus FailureStatus { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } + public string Name { get { throw null; } set { } } + public System.Collections.Generic.ISet Tags { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } } + public System.TimeSpan Timeout { get { throw null; } set { } } + } + [System.Runtime.InteropServices.StructLayoutAttribute(System.Runtime.InteropServices.LayoutKind.Sequential)] + public partial struct HealthCheckResult + { + private object _dummy; + private int _dummyPrimitive; + public HealthCheckResult(Microsoft.Extensions.Diagnostics.HealthChecks.HealthStatus status, string description = null, System.Exception exception = null, System.Collections.Generic.IReadOnlyDictionary data = null) { throw null; } + public System.Collections.Generic.IReadOnlyDictionary Data { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } } + public string Description { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } } + public System.Exception Exception { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } } + public Microsoft.Extensions.Diagnostics.HealthChecks.HealthStatus Status { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } } + public static Microsoft.Extensions.Diagnostics.HealthChecks.HealthCheckResult Degraded(string description = null, System.Exception exception = null, System.Collections.Generic.IReadOnlyDictionary data = null) { throw null; } + public static Microsoft.Extensions.Diagnostics.HealthChecks.HealthCheckResult Healthy(string description = null, System.Collections.Generic.IReadOnlyDictionary data = null) { throw null; } + public static Microsoft.Extensions.Diagnostics.HealthChecks.HealthCheckResult Unhealthy(string description = null, System.Exception exception = null, System.Collections.Generic.IReadOnlyDictionary data = null) { throw null; } + } + public sealed partial class HealthReport + { + public HealthReport(System.Collections.Generic.IReadOnlyDictionary entries, System.TimeSpan totalDuration) { } + public System.Collections.Generic.IReadOnlyDictionary Entries { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } } + public Microsoft.Extensions.Diagnostics.HealthChecks.HealthStatus Status { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } } + public System.TimeSpan TotalDuration { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } } + } + [System.Runtime.InteropServices.StructLayoutAttribute(System.Runtime.InteropServices.LayoutKind.Sequential)] + public partial struct HealthReportEntry + { + private object _dummy; + private int _dummyPrimitive; + public HealthReportEntry(Microsoft.Extensions.Diagnostics.HealthChecks.HealthStatus status, string description, System.TimeSpan duration, System.Exception exception, System.Collections.Generic.IReadOnlyDictionary data) { throw null; } + public HealthReportEntry(Microsoft.Extensions.Diagnostics.HealthChecks.HealthStatus status, string description, System.TimeSpan duration, System.Exception exception, System.Collections.Generic.IReadOnlyDictionary data, System.Collections.Generic.IEnumerable tags = null) { throw null; } + public System.Collections.Generic.IReadOnlyDictionary Data { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } } + public string Description { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } } + public System.TimeSpan Duration { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } } + public System.Exception Exception { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } } + public Microsoft.Extensions.Diagnostics.HealthChecks.HealthStatus Status { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } } + public System.Collections.Generic.IEnumerable Tags { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } } + } + public enum HealthStatus + { + Unhealthy = 0, + Degraded = 1, + Healthy = 2, + } + public partial interface IHealthCheck + { + System.Threading.Tasks.Task CheckHealthAsync(Microsoft.Extensions.Diagnostics.HealthChecks.HealthCheckContext context, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)); + } + public partial interface IHealthCheckPublisher + { + System.Threading.Tasks.Task PublishAsync(Microsoft.Extensions.Diagnostics.HealthChecks.HealthReport report, System.Threading.CancellationToken cancellationToken); + } +} diff --git a/src/HealthChecks/HealthChecks/ref/Microsoft.Extensions.Diagnostics.HealthChecks.csproj b/src/HealthChecks/HealthChecks/ref/Microsoft.Extensions.Diagnostics.HealthChecks.csproj index 2286087bc7..07f63049eb 100644 --- a/src/HealthChecks/HealthChecks/ref/Microsoft.Extensions.Diagnostics.HealthChecks.csproj +++ b/src/HealthChecks/HealthChecks/ref/Microsoft.Extensions.Diagnostics.HealthChecks.csproj @@ -1,7 +1,7 @@ - netstandard2.0;netcoreapp3.0 + netstandard2.0;netcoreapp5.0 @@ -9,8 +9,8 @@ - - + + diff --git a/src/HealthChecks/HealthChecks/ref/Microsoft.Extensions.Diagnostics.HealthChecks.netcoreapp5.0.cs b/src/HealthChecks/HealthChecks/ref/Microsoft.Extensions.Diagnostics.HealthChecks.netcoreapp5.0.cs new file mode 100644 index 0000000000..a23961efdd --- /dev/null +++ b/src/HealthChecks/HealthChecks/ref/Microsoft.Extensions.Diagnostics.HealthChecks.netcoreapp5.0.cs @@ -0,0 +1,59 @@ +// 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. + +namespace Microsoft.Extensions.DependencyInjection +{ + public static partial class HealthChecksBuilderAddCheckExtensions + { + public static Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder AddCheck(this Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder builder, string name, Microsoft.Extensions.Diagnostics.HealthChecks.IHealthCheck instance, Microsoft.Extensions.Diagnostics.HealthChecks.HealthStatus? failureStatus, System.Collections.Generic.IEnumerable tags) { throw null; } + public static Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder AddCheck(this Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder builder, string name, Microsoft.Extensions.Diagnostics.HealthChecks.IHealthCheck instance, Microsoft.Extensions.Diagnostics.HealthChecks.HealthStatus? failureStatus = default(Microsoft.Extensions.Diagnostics.HealthChecks.HealthStatus?), System.Collections.Generic.IEnumerable tags = null, System.TimeSpan? timeout = default(System.TimeSpan?)) { throw null; } + public static Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder AddCheck(this Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder builder, string name, Microsoft.Extensions.Diagnostics.HealthChecks.HealthStatus? failureStatus, System.Collections.Generic.IEnumerable tags) where T : class, Microsoft.Extensions.Diagnostics.HealthChecks.IHealthCheck { throw null; } + public static Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder AddCheck(this Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder builder, string name, Microsoft.Extensions.Diagnostics.HealthChecks.HealthStatus? failureStatus = default(Microsoft.Extensions.Diagnostics.HealthChecks.HealthStatus?), System.Collections.Generic.IEnumerable tags = null, System.TimeSpan? timeout = default(System.TimeSpan?)) where T : class, Microsoft.Extensions.Diagnostics.HealthChecks.IHealthCheck { throw null; } + public static Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder AddTypeActivatedCheck(this Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder builder, string name, Microsoft.Extensions.Diagnostics.HealthChecks.HealthStatus? failureStatus, System.Collections.Generic.IEnumerable tags, params object[] args) where T : class, Microsoft.Extensions.Diagnostics.HealthChecks.IHealthCheck { throw null; } + public static Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder AddTypeActivatedCheck(this Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder builder, string name, Microsoft.Extensions.Diagnostics.HealthChecks.HealthStatus? failureStatus, System.Collections.Generic.IEnumerable tags, System.TimeSpan timeout, params object[] args) where T : class, Microsoft.Extensions.Diagnostics.HealthChecks.IHealthCheck { throw null; } + public static Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder AddTypeActivatedCheck(this Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder builder, string name, Microsoft.Extensions.Diagnostics.HealthChecks.HealthStatus? failureStatus, params object[] args) where T : class, Microsoft.Extensions.Diagnostics.HealthChecks.IHealthCheck { throw null; } + public static Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder AddTypeActivatedCheck(this Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder builder, string name, params object[] args) where T : class, Microsoft.Extensions.Diagnostics.HealthChecks.IHealthCheck { throw null; } + } + public static partial class HealthChecksBuilderDelegateExtensions + { + public static Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder AddAsyncCheck(this Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder builder, string name, System.Func> check, System.Collections.Generic.IEnumerable tags) { throw null; } + public static Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder AddAsyncCheck(this Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder builder, string name, System.Func> check, System.Collections.Generic.IEnumerable tags = null, System.TimeSpan? timeout = default(System.TimeSpan?)) { throw null; } + public static Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder AddAsyncCheck(this Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder builder, string name, System.Func> check, System.Collections.Generic.IEnumerable tags) { throw null; } + public static Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder AddAsyncCheck(this Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder builder, string name, System.Func> check, System.Collections.Generic.IEnumerable tags = null, System.TimeSpan? timeout = default(System.TimeSpan?)) { throw null; } + public static Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder AddCheck(this Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder builder, string name, System.Func check, System.Collections.Generic.IEnumerable tags) { throw null; } + public static Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder AddCheck(this Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder builder, string name, System.Func check, System.Collections.Generic.IEnumerable tags = null, System.TimeSpan? timeout = default(System.TimeSpan?)) { throw null; } + public static Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder AddCheck(this Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder builder, string name, System.Func check, System.Collections.Generic.IEnumerable tags) { throw null; } + public static Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder AddCheck(this Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder builder, string name, System.Func check, System.Collections.Generic.IEnumerable tags = null, System.TimeSpan? timeout = default(System.TimeSpan?)) { throw null; } + } + public static partial class HealthCheckServiceCollectionExtensions + { + public static Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder AddHealthChecks(this Microsoft.Extensions.DependencyInjection.IServiceCollection services) { throw null; } + } + public partial interface IHealthChecksBuilder + { + Microsoft.Extensions.DependencyInjection.IServiceCollection Services { get; } + Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder Add(Microsoft.Extensions.Diagnostics.HealthChecks.HealthCheckRegistration registration); + } +} +namespace Microsoft.Extensions.Diagnostics.HealthChecks +{ + public sealed partial class HealthCheckPublisherOptions + { + public HealthCheckPublisherOptions() { } + public System.TimeSpan Delay { get { throw null; } set { } } + public System.TimeSpan Period { get { throw null; } set { } } + public System.Func Predicate { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } + public System.TimeSpan Timeout { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } + } + public abstract partial class HealthCheckService + { + protected HealthCheckService() { } + public abstract System.Threading.Tasks.Task CheckHealthAsync(System.Func predicate, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)); + public System.Threading.Tasks.Task CheckHealthAsync(System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) { throw null; } + } + public sealed partial class HealthCheckServiceOptions + { + public HealthCheckServiceOptions() { } + public System.Collections.Generic.ICollection Registrations { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } } + } +} diff --git a/src/JSInterop/Microsoft.JSInterop/ref/Microsoft.JSInterop.csproj b/src/JSInterop/Microsoft.JSInterop/ref/Microsoft.JSInterop.csproj index 61980aa920..ae19552c6d 100644 --- a/src/JSInterop/Microsoft.JSInterop/ref/Microsoft.JSInterop.csproj +++ b/src/JSInterop/Microsoft.JSInterop/ref/Microsoft.JSInterop.csproj @@ -1,14 +1,14 @@ - netstandard2.0;netcoreapp3.0 + netstandard2.0;netcoreapp5.0 - - + + diff --git a/src/JSInterop/Microsoft.JSInterop/ref/Microsoft.JSInterop.netcoreapp5.0.cs b/src/JSInterop/Microsoft.JSInterop/ref/Microsoft.JSInterop.netcoreapp5.0.cs new file mode 100644 index 0000000000..654ae9d617 --- /dev/null +++ b/src/JSInterop/Microsoft.JSInterop/ref/Microsoft.JSInterop.netcoreapp5.0.cs @@ -0,0 +1,64 @@ +// 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. + +namespace Microsoft.JSInterop +{ + public static partial class DotNetDispatcher + { + public static void BeginInvoke(string callId, string assemblyName, string methodIdentifier, long dotNetObjectId, string argsJson) { } + public static void EndInvoke(string arguments) { } + public static string Invoke(string assemblyName, string methodIdentifier, long dotNetObjectId, string argsJson) { throw null; } + [Microsoft.JSInterop.JSInvokableAttribute("DotNetDispatcher.ReleaseDotNetObject")] + public static void ReleaseDotNetObject(long dotNetObjectId) { } + } + public static partial class DotNetObjectRef + { + public static Microsoft.JSInterop.DotNetObjectRef Create(TValue value) where TValue : class { throw null; } + } + public sealed partial class DotNetObjectRef : System.IDisposable where TValue : class + { + internal DotNetObjectRef() { } + public TValue Value { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } } + public void Dispose() { } + } + public partial interface IJSInProcessRuntime : Microsoft.JSInterop.IJSRuntime + { + T Invoke(string identifier, params object[] args); + } + public partial interface IJSRuntime + { + System.Threading.Tasks.Task InvokeAsync(string identifier, System.Collections.Generic.IEnumerable args, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)); + System.Threading.Tasks.Task InvokeAsync(string identifier, params object[] args); + } + public partial class JSException : System.Exception + { + public JSException(string message) { } + public JSException(string message, System.Exception innerException) { } + } + public abstract partial class JSInProcessRuntimeBase : Microsoft.JSInterop.JSRuntimeBase, Microsoft.JSInterop.IJSInProcessRuntime, Microsoft.JSInterop.IJSRuntime + { + protected JSInProcessRuntimeBase() { } + protected abstract string InvokeJS(string identifier, string argsJson); + public TValue Invoke(string identifier, params object[] args) { throw null; } + } + [System.AttributeUsageAttribute(System.AttributeTargets.Method, AllowMultiple=true)] + public partial class JSInvokableAttribute : System.Attribute + { + public JSInvokableAttribute() { } + public JSInvokableAttribute(string identifier) { } + public string Identifier { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } } + } + public static partial class JSRuntime + { + public static void SetCurrentJSRuntime(Microsoft.JSInterop.IJSRuntime instance) { } + } + public abstract partial class JSRuntimeBase : Microsoft.JSInterop.IJSRuntime + { + protected JSRuntimeBase() { } + protected System.TimeSpan? DefaultAsyncTimeout { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } + protected abstract void BeginInvokeJS(long taskId, string identifier, string argsJson); + protected internal abstract void EndInvokeDotNet(string callId, bool success, object resultOrError, string assemblyName, string methodIdentifier, long dotNetObjectId); + public System.Threading.Tasks.Task InvokeAsync(string identifier, System.Collections.Generic.IEnumerable args, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) { throw null; } + public System.Threading.Tasks.Task InvokeAsync(string identifier, params object[] args) { throw null; } + } +} diff --git a/src/Localization/Abstractions/ref/Microsoft.Extensions.Localization.Abstractions.csproj b/src/Localization/Abstractions/ref/Microsoft.Extensions.Localization.Abstractions.csproj index 6404d5ae8e..66e71c0554 100644 --- a/src/Localization/Abstractions/ref/Microsoft.Extensions.Localization.Abstractions.csproj +++ b/src/Localization/Abstractions/ref/Microsoft.Extensions.Localization.Abstractions.csproj @@ -1,14 +1,14 @@ - netstandard2.0;netcoreapp3.0 + netstandard2.0;netcoreapp5.0 - - + + diff --git a/src/Localization/Abstractions/ref/Microsoft.Extensions.Localization.Abstractions.netcoreapp5.0.cs b/src/Localization/Abstractions/ref/Microsoft.Extensions.Localization.Abstractions.netcoreapp5.0.cs new file mode 100644 index 0000000000..174cac28e5 --- /dev/null +++ b/src/Localization/Abstractions/ref/Microsoft.Extensions.Localization.Abstractions.netcoreapp5.0.cs @@ -0,0 +1,49 @@ +// 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. + +namespace Microsoft.Extensions.Localization +{ + public partial interface IStringLocalizer + { + Microsoft.Extensions.Localization.LocalizedString this[string name] { get; } + Microsoft.Extensions.Localization.LocalizedString this[string name, params object[] arguments] { get; } + System.Collections.Generic.IEnumerable GetAllStrings(bool includeParentCultures); + [System.ObsoleteAttribute("This method is obsolete. Use `CurrentCulture` and `CurrentUICulture` instead.")] + Microsoft.Extensions.Localization.IStringLocalizer WithCulture(System.Globalization.CultureInfo culture); + } + public partial interface IStringLocalizerFactory + { + Microsoft.Extensions.Localization.IStringLocalizer Create(string baseName, string location); + Microsoft.Extensions.Localization.IStringLocalizer Create(System.Type resourceSource); + } + public partial interface IStringLocalizer : Microsoft.Extensions.Localization.IStringLocalizer + { + } + public partial class LocalizedString + { + public LocalizedString(string name, string value) { } + public LocalizedString(string name, string value, bool resourceNotFound) { } + public LocalizedString(string name, string value, bool resourceNotFound, string searchedLocation) { } + public string Name { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } } + public bool ResourceNotFound { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } } + public string SearchedLocation { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } } + public string Value { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } } + public static implicit operator string (Microsoft.Extensions.Localization.LocalizedString localizedString) { throw null; } + public override string ToString() { throw null; } + } + public static partial class StringLocalizerExtensions + { + public static System.Collections.Generic.IEnumerable GetAllStrings(this Microsoft.Extensions.Localization.IStringLocalizer stringLocalizer) { throw null; } + public static Microsoft.Extensions.Localization.LocalizedString GetString(this Microsoft.Extensions.Localization.IStringLocalizer stringLocalizer, string name) { throw null; } + public static Microsoft.Extensions.Localization.LocalizedString GetString(this Microsoft.Extensions.Localization.IStringLocalizer stringLocalizer, string name, params object[] arguments) { throw null; } + } + public partial class StringLocalizer : Microsoft.Extensions.Localization.IStringLocalizer, Microsoft.Extensions.Localization.IStringLocalizer + { + public StringLocalizer(Microsoft.Extensions.Localization.IStringLocalizerFactory factory) { } + public virtual Microsoft.Extensions.Localization.LocalizedString this[string name] { get { throw null; } } + public virtual Microsoft.Extensions.Localization.LocalizedString this[string name, params object[] arguments] { get { throw null; } } + public System.Collections.Generic.IEnumerable GetAllStrings(bool includeParentCultures) { throw null; } + [System.ObsoleteAttribute("This method is obsolete. Use `CurrentCulture` and `CurrentUICulture` instead.")] + public virtual Microsoft.Extensions.Localization.IStringLocalizer WithCulture(System.Globalization.CultureInfo culture) { throw null; } + } +} diff --git a/src/Localization/Localization/ref/Microsoft.Extensions.Localization.csproj b/src/Localization/Localization/ref/Microsoft.Extensions.Localization.csproj index 735277e754..2a84077b06 100644 --- a/src/Localization/Localization/ref/Microsoft.Extensions.Localization.csproj +++ b/src/Localization/Localization/ref/Microsoft.Extensions.Localization.csproj @@ -1,7 +1,7 @@ - netstandard2.0;netcoreapp3.0 + netstandard2.0;netcoreapp5.0 @@ -10,8 +10,8 @@ - - + + diff --git a/src/Localization/Localization/ref/Microsoft.Extensions.Localization.netcoreapp5.0.cs b/src/Localization/Localization/ref/Microsoft.Extensions.Localization.netcoreapp5.0.cs new file mode 100644 index 0000000000..80175da718 --- /dev/null +++ b/src/Localization/Localization/ref/Microsoft.Extensions.Localization.netcoreapp5.0.cs @@ -0,0 +1,93 @@ +// 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. + +namespace Microsoft.Extensions.DependencyInjection +{ + public static partial class LocalizationServiceCollectionExtensions + { + public static Microsoft.Extensions.DependencyInjection.IServiceCollection AddLocalization(this Microsoft.Extensions.DependencyInjection.IServiceCollection services) { throw null; } + public static Microsoft.Extensions.DependencyInjection.IServiceCollection AddLocalization(this Microsoft.Extensions.DependencyInjection.IServiceCollection services, System.Action setupAction) { throw null; } + } +} +namespace Microsoft.Extensions.Localization +{ + public partial interface IResourceNamesCache + { + System.Collections.Generic.IList GetOrAdd(string name, System.Func> valueFactory); + } + public partial class LocalizationOptions + { + public LocalizationOptions() { } + public string ResourcesPath { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } + } + [System.AttributeUsageAttribute(System.AttributeTargets.Assembly, AllowMultiple=false, Inherited=false)] + public partial class ResourceLocationAttribute : System.Attribute + { + public ResourceLocationAttribute(string resourceLocation) { } + public string ResourceLocation { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } } + } + public partial class ResourceManagerStringLocalizer : Microsoft.Extensions.Localization.IStringLocalizer + { + public ResourceManagerStringLocalizer(System.Resources.ResourceManager resourceManager, Microsoft.Extensions.Localization.Internal.AssemblyWrapper resourceAssemblyWrapper, string baseName, Microsoft.Extensions.Localization.IResourceNamesCache resourceNamesCache, Microsoft.Extensions.Logging.ILogger logger) { } + public ResourceManagerStringLocalizer(System.Resources.ResourceManager resourceManager, Microsoft.Extensions.Localization.Internal.IResourceStringProvider resourceStringProvider, string baseName, Microsoft.Extensions.Localization.IResourceNamesCache resourceNamesCache, Microsoft.Extensions.Logging.ILogger logger) { } + public ResourceManagerStringLocalizer(System.Resources.ResourceManager resourceManager, System.Reflection.Assembly resourceAssembly, string baseName, Microsoft.Extensions.Localization.IResourceNamesCache resourceNamesCache, Microsoft.Extensions.Logging.ILogger logger) { } + public virtual Microsoft.Extensions.Localization.LocalizedString this[string name] { get { throw null; } } + public virtual Microsoft.Extensions.Localization.LocalizedString this[string name, params object[] arguments] { get { throw null; } } + public virtual System.Collections.Generic.IEnumerable GetAllStrings(bool includeParentCultures) { throw null; } + protected System.Collections.Generic.IEnumerable GetAllStrings(bool includeParentCultures, System.Globalization.CultureInfo culture) { throw null; } + protected string GetStringSafely(string name, System.Globalization.CultureInfo culture) { throw null; } + [System.ObsoleteAttribute("This method is obsolete. Use `CurrentCulture` and `CurrentUICulture` instead.")] + public Microsoft.Extensions.Localization.IStringLocalizer WithCulture(System.Globalization.CultureInfo culture) { throw null; } + } + public partial class ResourceManagerStringLocalizerFactory : Microsoft.Extensions.Localization.IStringLocalizerFactory + { + public ResourceManagerStringLocalizerFactory(Microsoft.Extensions.Options.IOptions localizationOptions, Microsoft.Extensions.Logging.ILoggerFactory loggerFactory) { } + public Microsoft.Extensions.Localization.IStringLocalizer Create(string baseName, string location) { throw null; } + public Microsoft.Extensions.Localization.IStringLocalizer Create(System.Type resourceSource) { throw null; } + protected virtual Microsoft.Extensions.Localization.ResourceManagerStringLocalizer CreateResourceManagerStringLocalizer(System.Reflection.Assembly assembly, string baseName) { throw null; } + protected virtual Microsoft.Extensions.Localization.ResourceLocationAttribute GetResourceLocationAttribute(System.Reflection.Assembly assembly) { throw null; } + protected virtual string GetResourcePrefix(System.Reflection.TypeInfo typeInfo) { throw null; } + protected virtual string GetResourcePrefix(System.Reflection.TypeInfo typeInfo, string baseNamespace, string resourcesRelativePath) { throw null; } + protected virtual string GetResourcePrefix(string baseResourceName, string baseNamespace) { throw null; } + protected virtual string GetResourcePrefix(string location, string baseName, string resourceLocation) { throw null; } + protected virtual Microsoft.Extensions.Localization.RootNamespaceAttribute GetRootNamespaceAttribute(System.Reflection.Assembly assembly) { throw null; } + } + [System.ObsoleteAttribute("This method is obsolete. Use `CurrentCulture` and `CurrentUICulture` instead.")] + public partial class ResourceManagerWithCultureStringLocalizer : Microsoft.Extensions.Localization.ResourceManagerStringLocalizer + { + public ResourceManagerWithCultureStringLocalizer(System.Resources.ResourceManager resourceManager, System.Reflection.Assembly resourceAssembly, string baseName, Microsoft.Extensions.Localization.IResourceNamesCache resourceNamesCache, System.Globalization.CultureInfo culture, Microsoft.Extensions.Logging.ILogger logger) : base (default(System.Resources.ResourceManager), default(System.Reflection.Assembly), default(string), default(Microsoft.Extensions.Localization.IResourceNamesCache), default(Microsoft.Extensions.Logging.ILogger)) { } + public override Microsoft.Extensions.Localization.LocalizedString this[string name] { get { throw null; } } + public override Microsoft.Extensions.Localization.LocalizedString this[string name, params object[] arguments] { get { throw null; } } + public override System.Collections.Generic.IEnumerable GetAllStrings(bool includeParentCultures) { throw null; } + } + public partial class ResourceNamesCache : Microsoft.Extensions.Localization.IResourceNamesCache + { + public ResourceNamesCache() { } + public System.Collections.Generic.IList GetOrAdd(string name, System.Func> valueFactory) { throw null; } + } + [System.AttributeUsageAttribute(System.AttributeTargets.Assembly, AllowMultiple=false, Inherited=false)] + public partial class RootNamespaceAttribute : System.Attribute + { + public RootNamespaceAttribute(string rootNamespace) { } + public string RootNamespace { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } } + } +} +namespace Microsoft.Extensions.Localization.Internal +{ + public partial class AssemblyWrapper + { + public AssemblyWrapper(System.Reflection.Assembly assembly) { } + public System.Reflection.Assembly Assembly { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } } + public virtual string FullName { get { throw null; } } + public virtual System.IO.Stream GetManifestResourceStream(string name) { throw null; } + } + public partial interface IResourceStringProvider + { + System.Collections.Generic.IList GetAllResourceStrings(System.Globalization.CultureInfo culture, bool throwOnMissing); + } + public partial class ResourceManagerStringProvider : Microsoft.Extensions.Localization.Internal.IResourceStringProvider + { + public ResourceManagerStringProvider(Microsoft.Extensions.Localization.IResourceNamesCache resourceCache, System.Resources.ResourceManager resourceManager, System.Reflection.Assembly assembly, string baseName) { } + public System.Collections.Generic.IList GetAllResourceStrings(System.Globalization.CultureInfo culture, bool throwOnMissing) { throw null; } + } +} diff --git a/src/WebEncoders/ref/Microsoft.Extensions.WebEncoders.csproj b/src/WebEncoders/ref/Microsoft.Extensions.WebEncoders.csproj index 628691a220..3fa06a7280 100644 --- a/src/WebEncoders/ref/Microsoft.Extensions.WebEncoders.csproj +++ b/src/WebEncoders/ref/Microsoft.Extensions.WebEncoders.csproj @@ -1,7 +1,7 @@ - netstandard2.0;netcoreapp3.0 + netstandard2.0;netcoreapp5.0 @@ -9,8 +9,8 @@ - - + + diff --git a/src/WebEncoders/ref/Microsoft.Extensions.WebEncoders.netcoreapp5.0.cs b/src/WebEncoders/ref/Microsoft.Extensions.WebEncoders.netcoreapp5.0.cs new file mode 100644 index 0000000000..18cdcbdfa3 --- /dev/null +++ b/src/WebEncoders/ref/Microsoft.Extensions.WebEncoders.netcoreapp5.0.cs @@ -0,0 +1,55 @@ +// 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. + +namespace Microsoft.Extensions.DependencyInjection +{ + public static partial class EncoderServiceCollectionExtensions + { + public static Microsoft.Extensions.DependencyInjection.IServiceCollection AddWebEncoders(this Microsoft.Extensions.DependencyInjection.IServiceCollection services) { throw null; } + public static Microsoft.Extensions.DependencyInjection.IServiceCollection AddWebEncoders(this Microsoft.Extensions.DependencyInjection.IServiceCollection services, System.Action setupAction) { throw null; } + } +} +namespace Microsoft.Extensions.WebEncoders +{ + public sealed partial class WebEncoderOptions + { + public WebEncoderOptions() { } + public System.Text.Encodings.Web.TextEncoderSettings TextEncoderSettings { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } + } +} +namespace Microsoft.Extensions.WebEncoders.Testing +{ + public sealed partial class HtmlTestEncoder : System.Text.Encodings.Web.HtmlEncoder + { + public HtmlTestEncoder() { } + public override int MaxOutputCharactersPerInputCharacter { get { throw null; } } + public override void Encode(System.IO.TextWriter output, char[] value, int startIndex, int characterCount) { } + public override void Encode(System.IO.TextWriter output, string value, int startIndex, int characterCount) { } + public override string Encode(string value) { throw null; } + public unsafe override int FindFirstCharacterToEncode(char* text, int textLength) { throw null; } + public unsafe override bool TryEncodeUnicodeScalar(int unicodeScalar, char* buffer, int bufferLength, out int numberOfCharactersWritten) { throw null; } + public override bool WillEncode(int unicodeScalar) { throw null; } + } + public partial class JavaScriptTestEncoder : System.Text.Encodings.Web.JavaScriptEncoder + { + public JavaScriptTestEncoder() { } + public override int MaxOutputCharactersPerInputCharacter { get { throw null; } } + public override void Encode(System.IO.TextWriter output, char[] value, int startIndex, int characterCount) { } + public override void Encode(System.IO.TextWriter output, string value, int startIndex, int characterCount) { } + public override string Encode(string value) { throw null; } + public unsafe override int FindFirstCharacterToEncode(char* text, int textLength) { throw null; } + public unsafe override bool TryEncodeUnicodeScalar(int unicodeScalar, char* buffer, int bufferLength, out int numberOfCharactersWritten) { throw null; } + public override bool WillEncode(int unicodeScalar) { throw null; } + } + public partial class UrlTestEncoder : System.Text.Encodings.Web.UrlEncoder + { + public UrlTestEncoder() { } + public override int MaxOutputCharactersPerInputCharacter { get { throw null; } } + public override void Encode(System.IO.TextWriter output, char[] value, int startIndex, int characterCount) { } + public override void Encode(System.IO.TextWriter output, string value, int startIndex, int characterCount) { } + public override string Encode(string value) { throw null; } + public unsafe override int FindFirstCharacterToEncode(char* text, int textLength) { throw null; } + public unsafe override bool TryEncodeUnicodeScalar(int unicodeScalar, char* buffer, int bufferLength, out int numberOfCharactersWritten) { throw null; } + public override bool WillEncode(int unicodeScalar) { throw null; } + } +} From 60778e8dc7d11cd24e2fd368784db47cd2b18ce5 Mon Sep 17 00:00:00 2001 From: Doug Bunting <6431421+dougbu@users.noreply.github.com> Date: Sun, 11 Aug 2019 11:26:50 -0700 Subject: [PATCH 0154/1101] Fix unintended breaking change - blocking aspnet/AspNetCoredotnet/extensions#1293 dependency update PR - commit 3ec8c35e450c lost `AssemblyName` substitution - tasks assembly in package incorrectly named ".Manifest.Task.dll" \n\nCommit migrated from https://github.com/dotnet/extensions/commit/77403f35be5559b01c7d10074f3670e636c49811 --- .../src/Microsoft.Extensions.FileProviders.Embedded.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/FileProviders/Embedded/src/Microsoft.Extensions.FileProviders.Embedded.csproj b/src/FileProviders/Embedded/src/Microsoft.Extensions.FileProviders.Embedded.csproj index 115d9affe1..d36323e3b1 100644 --- a/src/FileProviders/Embedded/src/Microsoft.Extensions.FileProviders.Embedded.csproj +++ b/src/FileProviders/Embedded/src/Microsoft.Extensions.FileProviders.Embedded.csproj @@ -26,7 +26,7 @@ - + From d299dff853ea08e5811048dfadec1dfd0f184d87 Mon Sep 17 00:00:00 2001 From: Stephen Halter Date: Mon, 12 Aug 2019 17:42:55 -0700 Subject: [PATCH 0155/1101] Throw NotSupportedException given FileHandleEndPoint - Kestrel's default socket transport does not support FileHandleEndPoint - Throw a NotSupportedException instead of a NotImplementedException --- .../src/SocketConnectionListener.cs | 7 ++++++ .../Transport.Sockets/src/SocketsStrings.resx | 3 +++ .../SocketTransportFactoryTests.cs | 25 +++++++++++++++++++ 3 files changed, 35 insertions(+) create mode 100644 src/Servers/Kestrel/test/Sockets.BindTests/SocketTransportFactoryTests.cs diff --git a/src/Servers/Kestrel/Transport.Sockets/src/SocketConnectionListener.cs b/src/Servers/Kestrel/Transport.Sockets/src/SocketConnectionListener.cs index ccdb774674..600d674d98 100644 --- a/src/Servers/Kestrel/Transport.Sockets/src/SocketConnectionListener.cs +++ b/src/Servers/Kestrel/Transport.Sockets/src/SocketConnectionListener.cs @@ -62,6 +62,13 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Transport.Sockets throw new InvalidOperationException(SocketsStrings.TransportAlreadyBound); } + // Check if EndPoint is a FileHandleEndpoint before attempting to access EndPoint.AddressFamily + // since that will throw an NotImplementedException. + if (EndPoint is FileHandleEndPoint) + { + throw new NotSupportedException(SocketsStrings.FileHandleEndPointNotSupported); + } + Socket listenSocket; // Unix domain sockets are unspecified diff --git a/src/Servers/Kestrel/Transport.Sockets/src/SocketsStrings.resx b/src/Servers/Kestrel/Transport.Sockets/src/SocketsStrings.resx index 52b26c66bc..58c6581609 100644 --- a/src/Servers/Kestrel/Transport.Sockets/src/SocketsStrings.resx +++ b/src/Servers/Kestrel/Transport.Sockets/src/SocketsStrings.resx @@ -117,6 +117,9 @@ System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + The Socket transport does not support FileHandleEndPoints. Consider using the libuv transport instead. + Only ListenType.IPEndPoint is supported by the Socket Transport. https://go.microsoft.com/fwlink/?linkid=874850 diff --git a/src/Servers/Kestrel/test/Sockets.BindTests/SocketTransportFactoryTests.cs b/src/Servers/Kestrel/test/Sockets.BindTests/SocketTransportFactoryTests.cs new file mode 100644 index 0000000000..2ff92f497f --- /dev/null +++ b/src/Servers/Kestrel/test/Sockets.BindTests/SocketTransportFactoryTests.cs @@ -0,0 +1,25 @@ +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using System; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Connections; +using Microsoft.AspNetCore.Server.Kestrel.Transport.Sockets; +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Options; +using Moq; +using Xunit; + +namespace Sockets.BindTests +{ + public class SocketTransportFactoryTests + { + [Fact] + public async Task ThrowsNotSupportedExceptionWhenBindingToFileHandleEndPoint() + { + var socketTransportFactory = new SocketTransportFactory(Options.Create(new SocketTransportOptions()), Mock.Of()); + await Assert.ThrowsAsync(async () => await socketTransportFactory.BindAsync(new FileHandleEndPoint(0, FileHandleType.Auto))); + } + } +} + From 4bb8a79efb2afffbfc2bc1ac5d9333b980534f92 Mon Sep 17 00:00:00 2001 From: Stephen Halter Date: Mon, 12 Aug 2019 19:28:29 -0700 Subject: [PATCH 0156/1101] Update src/Servers/Kestrel/Transport.Sockets/src/SocketsStrings.resx --- src/Servers/Kestrel/Transport.Sockets/src/SocketsStrings.resx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Servers/Kestrel/Transport.Sockets/src/SocketsStrings.resx b/src/Servers/Kestrel/Transport.Sockets/src/SocketsStrings.resx index 58c6581609..5f1475a1cf 100644 --- a/src/Servers/Kestrel/Transport.Sockets/src/SocketsStrings.resx +++ b/src/Servers/Kestrel/Transport.Sockets/src/SocketsStrings.resx @@ -118,7 +118,7 @@ System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - The Socket transport does not support FileHandleEndPoints. Consider using the libuv transport instead. + The Socket transport does not support binding to file handles. Consider using the libuv transport instead. Only ListenType.IPEndPoint is supported by the Socket Transport. https://go.microsoft.com/fwlink/?linkid=874850 @@ -126,4 +126,4 @@ Transport is already bound. - \ No newline at end of file + From edd5f54bc37af4f6ad444976dd7d6f5fe0ee0733 Mon Sep 17 00:00:00 2001 From: Pranav K Date: Wed, 14 Aug 2019 09:44:33 -0700 Subject: [PATCH 0157/1101] Special case Disposing DotNetObjectReferences (dotnet/extensions#2176) * Special case Disposing DotNetObjectReferences This removes a public JSInvokable method required for disposing DotNetObjectReferences \n\nCommit migrated from https://github.com/dotnet/extensions/commit/d6bfc28e2104066dc363bf79bed39d6580977b26 --- .../src/src/Microsoft.JSInterop.ts | 11 ++-- .../ref/Microsoft.JSInterop.netcoreapp3.0.cs | 4 +- .../ref/Microsoft.JSInterop.netstandard2.0.cs | 4 +- .../src/DotNetDispatcher.cs | 45 +++++++-------- .../src/DotNetObjectRef.cs | 3 +- .../src/DotNetObjectRefManager.cs | 6 +- .../src/DotNetObjectRefOfT.cs | 56 ++++++++++++++++--- .../src/DotNetObjectReferenceJsonConverter.cs | 4 +- .../src/IDotNetObjectRef.cs | 1 + .../test/DotNetDispatcherTest.cs | 20 ++++++- .../test/DotNetObjectRefTest.cs | 3 +- .../test/JSInProcessRuntimeBaseTest.cs | 6 +- .../test/JSRuntimeBaseTest.cs | 8 +-- 13 files changed, 109 insertions(+), 62 deletions(-) diff --git a/src/JSInterop/Microsoft.JSInterop.JS/src/src/Microsoft.JSInterop.ts b/src/JSInterop/Microsoft.JSInterop.JS/src/src/Microsoft.JSInterop.ts index 60f6f800a6..30a91bde4d 100644 --- a/src/JSInterop/Microsoft.JSInterop.JS/src/src/Microsoft.JSInterop.ts +++ b/src/JSInterop/Microsoft.JSInterop.JS/src/src/Microsoft.JSInterop.ts @@ -66,7 +66,11 @@ module DotNet { } } - function invokePossibleInstanceMethodAsync(assemblyName: string | null, methodIdentifier: string, dotNetObjectId: number | null, args: any[]): Promise { + function invokePossibleInstanceMethodAsync(assemblyName: string | null, methodIdentifier: string, dotNetObjectId: number | null, ...args: any[]): Promise { + if (assemblyName && dotNetObjectId) { + throw new Error(`For instance method calls, assemblyName should be null. Received '${assemblyName}'.`) ; + } + const asyncCallId = nextAsyncCallId++; const resultPromise = new Promise((resolve, reject) => { pendingAsyncCalls[asyncCallId] = { resolve, reject }; @@ -269,10 +273,7 @@ module DotNet { } public dispose() { - const promise = invokeMethodAsync( - 'Microsoft.JSInterop', - 'DotNetDispatcher.ReleaseDotNetObject', - this._id); + const promise = invokePossibleInstanceMethodAsync(null, '__Dispose', this._id); promise.catch(error => console.error(error)); } diff --git a/src/JSInterop/Microsoft.JSInterop/ref/Microsoft.JSInterop.netcoreapp3.0.cs b/src/JSInterop/Microsoft.JSInterop/ref/Microsoft.JSInterop.netcoreapp3.0.cs index 654ae9d617..e73fa1be69 100644 --- a/src/JSInterop/Microsoft.JSInterop/ref/Microsoft.JSInterop.netcoreapp3.0.cs +++ b/src/JSInterop/Microsoft.JSInterop/ref/Microsoft.JSInterop.netcoreapp3.0.cs @@ -8,8 +8,6 @@ namespace Microsoft.JSInterop public static void BeginInvoke(string callId, string assemblyName, string methodIdentifier, long dotNetObjectId, string argsJson) { } public static void EndInvoke(string arguments) { } public static string Invoke(string assemblyName, string methodIdentifier, long dotNetObjectId, string argsJson) { throw null; } - [Microsoft.JSInterop.JSInvokableAttribute("DotNetDispatcher.ReleaseDotNetObject")] - public static void ReleaseDotNetObject(long dotNetObjectId) { } } public static partial class DotNetObjectRef { @@ -18,7 +16,7 @@ namespace Microsoft.JSInterop public sealed partial class DotNetObjectRef : System.IDisposable where TValue : class { internal DotNetObjectRef() { } - public TValue Value { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } } + public TValue Value { get { throw null; } } public void Dispose() { } } public partial interface IJSInProcessRuntime : Microsoft.JSInterop.IJSRuntime diff --git a/src/JSInterop/Microsoft.JSInterop/ref/Microsoft.JSInterop.netstandard2.0.cs b/src/JSInterop/Microsoft.JSInterop/ref/Microsoft.JSInterop.netstandard2.0.cs index 654ae9d617..e73fa1be69 100644 --- a/src/JSInterop/Microsoft.JSInterop/ref/Microsoft.JSInterop.netstandard2.0.cs +++ b/src/JSInterop/Microsoft.JSInterop/ref/Microsoft.JSInterop.netstandard2.0.cs @@ -8,8 +8,6 @@ namespace Microsoft.JSInterop public static void BeginInvoke(string callId, string assemblyName, string methodIdentifier, long dotNetObjectId, string argsJson) { } public static void EndInvoke(string arguments) { } public static string Invoke(string assemblyName, string methodIdentifier, long dotNetObjectId, string argsJson) { throw null; } - [Microsoft.JSInterop.JSInvokableAttribute("DotNetDispatcher.ReleaseDotNetObject")] - public static void ReleaseDotNetObject(long dotNetObjectId) { } } public static partial class DotNetObjectRef { @@ -18,7 +16,7 @@ namespace Microsoft.JSInterop public sealed partial class DotNetObjectRef : System.IDisposable where TValue : class { internal DotNetObjectRef() { } - public TValue Value { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } } + public TValue Value { get { throw null; } } public void Dispose() { } } public partial interface IJSInProcessRuntime : Microsoft.JSInterop.IJSRuntime diff --git a/src/JSInterop/Microsoft.JSInterop/src/DotNetDispatcher.cs b/src/JSInterop/Microsoft.JSInterop/src/DotNetDispatcher.cs index 0dfac228a6..e639a33ff2 100644 --- a/src/JSInterop/Microsoft.JSInterop/src/DotNetDispatcher.cs +++ b/src/JSInterop/Microsoft.JSInterop/src/DotNetDispatcher.cs @@ -18,6 +18,7 @@ namespace Microsoft.JSInterop /// public static class DotNetDispatcher { + private const string DisposeDotNetObjectReferenceMethodName = "__Dispose"; internal static readonly JsonEncodedText DotNetObjectRefKey = JsonEncodedText.Encode("__dotNetObject"); private static readonly ConcurrentDictionary> _cachedMethodsByAssembly @@ -38,7 +39,7 @@ namespace Microsoft.JSInterop // the targeted method has [JSInvokable]. It is not itself subject to that restriction, // because there would be nobody to police that. This method *is* the police. - var targetInstance = (object)null; + IDotNetObjectRef targetInstance = default; if (dotNetObjectId != default) { targetInstance = DotNetObjectRefManager.Current.FindDotNetObject(dotNetObjectId); @@ -78,7 +79,7 @@ namespace Microsoft.JSInterop // original stack traces. object syncResult = null; ExceptionDispatchInfo syncException = null; - object targetInstance = null; + IDotNetObjectRef targetInstance = null; try { @@ -127,21 +128,28 @@ namespace Microsoft.JSInterop } } - private static object InvokeSynchronously(string assemblyName, string methodIdentifier, object targetInstance, string argsJson) + private static object InvokeSynchronously(string assemblyName, string methodIdentifier, IDotNetObjectRef objectReference, string argsJson) { AssemblyKey assemblyKey; - if (targetInstance != null) + if (objectReference is null) + { + assemblyKey = new AssemblyKey(assemblyName); + } + else { if (assemblyName != null) { throw new ArgumentException($"For instance method calls, '{nameof(assemblyName)}' should be null. Value received: '{assemblyName}'."); } - assemblyKey = new AssemblyKey(targetInstance.GetType().Assembly); - } - else - { - assemblyKey = new AssemblyKey(assemblyName); + if (string.Equals(DisposeDotNetObjectReferenceMethodName, methodIdentifier, StringComparison.Ordinal)) + { + // The client executed dotNetObjectReference.dispose(). Dispose the reference and exit. + objectReference.Dispose(); + return default; + } + + assemblyKey = new AssemblyKey(objectReference.Value.GetType().Assembly); } var (methodInfo, parameterTypes) = GetCachedMethodInfo(assemblyKey, methodIdentifier); @@ -150,7 +158,8 @@ namespace Microsoft.JSInterop try { - return methodInfo.Invoke(targetInstance, suppliedArgs); + // objectReference will be null if this call invokes a static JSInvokable method. + return methodInfo.Invoke(objectReference?.Value, suppliedArgs); } catch (TargetInvocationException tie) // Avoid using exception filters for AOT runtime support { @@ -280,22 +289,6 @@ namespace Microsoft.JSInterop } } - /// - /// Releases the reference to the specified .NET object. This allows the .NET runtime - /// to garbage collect that object if there are no other references to it. - /// - /// To avoid leaking memory, the JavaScript side code must call this for every .NET - /// object it obtains a reference to. The exception is if that object is used for - /// the entire lifetime of a given user's session, in which case it is released - /// automatically when the JavaScript runtime is disposed. - /// - /// The identifier previously passed to JavaScript code. - [JSInvokable(nameof(DotNetDispatcher) + "." + nameof(ReleaseDotNetObject))] - public static void ReleaseDotNetObject(long dotNetObjectId) - { - DotNetObjectRefManager.Current.ReleaseDotNetObject(dotNetObjectId); - } - private static (MethodInfo, Type[]) GetCachedMethodInfo(AssemblyKey assemblyKey, string methodIdentifier) { if (string.IsNullOrWhiteSpace(assemblyKey.AssemblyName)) diff --git a/src/JSInterop/Microsoft.JSInterop/src/DotNetObjectRef.cs b/src/JSInterop/Microsoft.JSInterop/src/DotNetObjectRef.cs index af790281e9..f604bab272 100644 --- a/src/JSInterop/Microsoft.JSInterop/src/DotNetObjectRef.cs +++ b/src/JSInterop/Microsoft.JSInterop/src/DotNetObjectRef.cs @@ -15,8 +15,7 @@ namespace Microsoft.JSInterop /// An instance of . public static DotNetObjectRef Create(TValue value) where TValue : class { - var objectId = DotNetObjectRefManager.Current.TrackObject(value); - return new DotNetObjectRef(objectId, value); + return new DotNetObjectRef(DotNetObjectRefManager.Current, value); } } } diff --git a/src/JSInterop/Microsoft.JSInterop/src/DotNetObjectRefManager.cs b/src/JSInterop/Microsoft.JSInterop/src/DotNetObjectRefManager.cs index ad1469e38f..a6be6aeb4f 100644 --- a/src/JSInterop/Microsoft.JSInterop/src/DotNetObjectRefManager.cs +++ b/src/JSInterop/Microsoft.JSInterop/src/DotNetObjectRefManager.cs @@ -10,7 +10,7 @@ namespace Microsoft.JSInterop internal class DotNetObjectRefManager { private long _nextId = 0; // 0 signals no object, but we increment prior to assignment. The first tracked object should have id 1 - private readonly ConcurrentDictionary _trackedRefsById = new ConcurrentDictionary(); + private readonly ConcurrentDictionary _trackedRefsById = new ConcurrentDictionary(); public static DotNetObjectRefManager Current { @@ -25,7 +25,7 @@ namespace Microsoft.JSInterop } } - public long TrackObject(object dotNetObjectRef) + public long TrackObject(IDotNetObjectRef dotNetObjectRef) { var dotNetObjectId = Interlocked.Increment(ref _nextId); _trackedRefsById[dotNetObjectId] = dotNetObjectRef; @@ -33,7 +33,7 @@ namespace Microsoft.JSInterop return dotNetObjectId; } - public object FindDotNetObject(long dotNetObjectId) + public IDotNetObjectRef FindDotNetObject(long dotNetObjectId) { return _trackedRefsById.TryGetValue(dotNetObjectId, out var dotNetObjectRef) ? dotNetObjectRef diff --git a/src/JSInterop/Microsoft.JSInterop/src/DotNetObjectRefOfT.cs b/src/JSInterop/Microsoft.JSInterop/src/DotNetObjectRefOfT.cs index be6bf91663..d83d0e89bb 100644 --- a/src/JSInterop/Microsoft.JSInterop/src/DotNetObjectRefOfT.cs +++ b/src/JSInterop/Microsoft.JSInterop/src/DotNetObjectRefOfT.cs @@ -16,23 +16,53 @@ namespace Microsoft.JSInterop [JsonConverter(typeof(DotNetObjectReferenceJsonConverterFactory))] public sealed class DotNetObjectRef : IDotNetObjectRef, IDisposable where TValue : class { + private readonly DotNetObjectRefManager _referenceManager; + private readonly TValue _value; + private readonly long _objectId; + /// /// Initializes a new instance of . /// - /// The object Id. + /// /// The value to pass by reference. - internal DotNetObjectRef(long objectId, TValue value) + internal DotNetObjectRef(DotNetObjectRefManager referenceManager, TValue value) { - ObjectId = objectId; - Value = value; + _referenceManager = referenceManager; + _objectId = _referenceManager.TrackObject(this); + _value = value; + } + + internal DotNetObjectRef(DotNetObjectRefManager referenceManager, long objectId, TValue value) + { + _referenceManager = referenceManager; + _objectId = objectId; + _value = value; } /// /// Gets the object instance represented by this wrapper. /// - public TValue Value { get; } + public TValue Value + { + get + { + ThrowIfDisposed(); + return _value; + } + } - internal long ObjectId { get; } + internal long ObjectId + { + get + { + ThrowIfDisposed(); + return _objectId; + } + } + + object IDotNetObjectRef.Value => Value; + + internal bool Disposed { get; private set; } /// /// Stops tracking this object reference, allowing it to be garbage collected @@ -41,7 +71,19 @@ namespace Microsoft.JSInterop /// public void Dispose() { - DotNetObjectRefManager.Current.ReleaseDotNetObject(ObjectId); + if (!Disposed) + { + Disposed = true; + _referenceManager.ReleaseDotNetObject(_objectId); + } + } + + private void ThrowIfDisposed() + { + if (Disposed) + { + throw new ObjectDisposedException(GetType().Name); + } } } } diff --git a/src/JSInterop/Microsoft.JSInterop/src/DotNetObjectReferenceJsonConverter.cs b/src/JSInterop/Microsoft.JSInterop/src/DotNetObjectReferenceJsonConverter.cs index eaabdbf9e6..71bfa28ad5 100644 --- a/src/JSInterop/Microsoft.JSInterop/src/DotNetObjectReferenceJsonConverter.cs +++ b/src/JSInterop/Microsoft.JSInterop/src/DotNetObjectReferenceJsonConverter.cs @@ -40,8 +40,8 @@ namespace Microsoft.JSInterop throw new JsonException($"Required property {DotNetObjectRefKey} not found."); } - var value = (TValue)DotNetObjectRefManager.Current.FindDotNetObject(dotNetObjectId); - return new DotNetObjectRef(dotNetObjectId, value); + var referenceManager = DotNetObjectRefManager.Current; + return (DotNetObjectRef)referenceManager.FindDotNetObject(dotNetObjectId); } public override void Write(Utf8JsonWriter writer, DotNetObjectRef value, JsonSerializerOptions options) diff --git a/src/JSInterop/Microsoft.JSInterop/src/IDotNetObjectRef.cs b/src/JSInterop/Microsoft.JSInterop/src/IDotNetObjectRef.cs index b082d0ce10..da16fa60a0 100644 --- a/src/JSInterop/Microsoft.JSInterop/src/IDotNetObjectRef.cs +++ b/src/JSInterop/Microsoft.JSInterop/src/IDotNetObjectRef.cs @@ -7,5 +7,6 @@ namespace Microsoft.JSInterop { internal interface IDotNetObjectRef : IDisposable { + object Value { get; } } } diff --git a/src/JSInterop/Microsoft.JSInterop/test/DotNetDispatcherTest.cs b/src/JSInterop/Microsoft.JSInterop/test/DotNetDispatcherTest.cs index 282aa0f364..13e01c2304 100644 --- a/src/JSInterop/Microsoft.JSInterop/test/DotNetDispatcherTest.cs +++ b/src/JSInterop/Microsoft.JSInterop/test/DotNetDispatcherTest.cs @@ -142,7 +142,7 @@ namespace Microsoft.JSInterop Assert.False(resultDto2Ref.TryGetProperty(nameof(TestDTO.IntVal), out _)); Assert.True(resultDto2Ref.TryGetProperty(DotNetDispatcher.DotNetObjectRefKey.EncodedUtf8Bytes, out var property)); - var resultDto2 = Assert.IsType(DotNetObjectRefManager.Current.FindDotNetObject(property.GetInt64())); + var resultDto2 = Assert.IsType>(DotNetObjectRefManager.Current.FindDotNetObject(property.GetInt64())).Value; Assert.Equal("MY STRING", resultDto2.StringVal); Assert.Equal(1299, resultDto2.IntVal); }); @@ -202,6 +202,20 @@ namespace Microsoft.JSInterop Assert.True(targetInstance.DidInvokeMyBaseClassInvocableInstanceVoid); }); + [Fact] + public Task DotNetObjectReferencesCanBeDisposed() => WithJSRuntime(jsRuntime => + { + // Arrange + var targetInstance = new SomePublicType(); + var objectRef = DotNetObjectRef.Create(targetInstance); + + // Act + DotNetDispatcher.BeginInvoke(null, null, "__Dispose", objectRef.ObjectId, null); + + // Assert + Assert.True(objectRef.Disposed); + }); + [Fact] public Task CannotUseDotNetObjectRefAfterDisposal() => WithJSRuntime(jsRuntime => { @@ -230,7 +244,7 @@ namespace Microsoft.JSInterop var targetInstance = new SomePublicType(); var objectRef = DotNetObjectRef.Create(targetInstance); jsRuntime.Invoke("unimportant", objectRef); - DotNetDispatcher.ReleaseDotNetObject(1); + objectRef.Dispose(); // Act/Assert var ex = Assert.Throws( @@ -320,7 +334,7 @@ namespace Microsoft.JSInterop // Assert Assert.Equal("[\"You passed myvalue\",{\"__dotNetObject\":3}]", resultJson); - var resultDto = (TestDTO)jsRuntime.ObjectRefManager.FindDotNetObject(3); + var resultDto = ((DotNetObjectRef)jsRuntime.ObjectRefManager.FindDotNetObject(3)).Value; Assert.Equal(1235, resultDto.IntVal); Assert.Equal("MY STRING", resultDto.StringVal); }); diff --git a/src/JSInterop/Microsoft.JSInterop/test/DotNetObjectRefTest.cs b/src/JSInterop/Microsoft.JSInterop/test/DotNetObjectRefTest.cs index 112363869e..22cb471f28 100644 --- a/src/JSInterop/Microsoft.JSInterop/test/DotNetObjectRefTest.cs +++ b/src/JSInterop/Microsoft.JSInterop/test/DotNetObjectRefTest.cs @@ -24,10 +24,11 @@ namespace Microsoft.JSInterop var objRef = DotNetObjectRef.Create(new object()); // Act + Assert.Equal(1, objRef.ObjectId); objRef.Dispose(); // Assert - var ex = Assert.Throws(() => jsRuntime.ObjectRefManager.FindDotNetObject(objRef.ObjectId)); + var ex = Assert.Throws(() => jsRuntime.ObjectRefManager.FindDotNetObject(1)); Assert.StartsWith("There is no tracked object with id '1'.", ex.Message); }); } diff --git a/src/JSInterop/Microsoft.JSInterop/test/JSInProcessRuntimeBaseTest.cs b/src/JSInterop/Microsoft.JSInterop/test/JSInProcessRuntimeBaseTest.cs index a8d551e94e..d71969d450 100644 --- a/src/JSInterop/Microsoft.JSInterop/test/JSInProcessRuntimeBaseTest.cs +++ b/src/JSInterop/Microsoft.JSInterop/test/JSInProcessRuntimeBaseTest.cs @@ -60,9 +60,9 @@ namespace Microsoft.JSInterop.Tests Assert.Equal("[{\"__dotNetObject\":1},{\"obj2\":{\"__dotNetObject\":2},\"obj3\":{\"__dotNetObject\":3}}]", call.ArgsJson); // Assert: Objects were tracked - Assert.Same(obj1, runtime.ObjectRefManager.FindDotNetObject(1)); - Assert.Same(obj2, runtime.ObjectRefManager.FindDotNetObject(2)); - Assert.Same(obj3, runtime.ObjectRefManager.FindDotNetObject(3)); + Assert.Same(obj1, runtime.ObjectRefManager.FindDotNetObject(1).Value); + Assert.Same(obj2, runtime.ObjectRefManager.FindDotNetObject(2).Value); + Assert.Same(obj3, runtime.ObjectRefManager.FindDotNetObject(3).Value); } [Fact] diff --git a/src/JSInterop/Microsoft.JSInterop/test/JSRuntimeBaseTest.cs b/src/JSInterop/Microsoft.JSInterop/test/JSRuntimeBaseTest.cs index 2714886f9a..c3bf4f9eef 100644 --- a/src/JSInterop/Microsoft.JSInterop/test/JSRuntimeBaseTest.cs +++ b/src/JSInterop/Microsoft.JSInterop/test/JSRuntimeBaseTest.cs @@ -282,10 +282,10 @@ namespace Microsoft.JSInterop Assert.Equal("[{\"__dotNetObject\":1},{\"obj2\":{\"__dotNetObject\":3},\"obj3\":{\"__dotNetObject\":4},\"obj1SameRef\":{\"__dotNetObject\":1},\"obj1DifferentRef\":{\"__dotNetObject\":2}}]", call.ArgsJson); // Assert: Objects were tracked - Assert.Same(obj1, runtime.ObjectRefManager.FindDotNetObject(1)); - Assert.Same(obj1, runtime.ObjectRefManager.FindDotNetObject(2)); - Assert.Same(obj2, runtime.ObjectRefManager.FindDotNetObject(3)); - Assert.Same(obj3, runtime.ObjectRefManager.FindDotNetObject(4)); + Assert.Same(obj1, runtime.ObjectRefManager.FindDotNetObject(1).Value); + Assert.Same(obj1, runtime.ObjectRefManager.FindDotNetObject(2).Value); + Assert.Same(obj2, runtime.ObjectRefManager.FindDotNetObject(3).Value); + Assert.Same(obj3, runtime.ObjectRefManager.FindDotNetObject(4).Value); } [Fact] From 9372816b7c436e06da6d6426e0863b3677aac83c Mon Sep 17 00:00:00 2001 From: Pranav K Date: Wed, 14 Aug 2019 12:24:35 -0700 Subject: [PATCH 0158/1101] API Review: JSRuntime (dotnet/extensions#2166) * API Review: JSRuntime * Rename JSRuntimeBase -> JSRuntime * Rename JSInProcessRuntimeBase -> JSInProcessRuntime * Rename DotNetObjectRef -> DotNetObjectReference * Update JSRuntime to return ValueTask * Make InvokeAsync APIs that explicitly cancels and API that default cancels more crisp * Introduce void invoking APIs * Fixup method names on DotNetDispatcher \n\nCommit migrated from https://github.com/dotnet/extensions/commit/93d3ae448551cac29af8cf882b31047f5da0dadc --- .../ref/Microsoft.JSInterop.netcoreapp3.0.cs | 63 +-- .../ref/Microsoft.JSInterop.netstandard2.0.cs | 63 +-- ...tObjectRef.cs => DotNetObjectReference.cs} | 14 +- ...tRefOfT.cs => DotNetObjectReferenceOfT.cs} | 18 +- .../Microsoft.JSInterop/src/IJSRuntime.cs | 14 +- .../{ => Infrastructure}/DotNetDispatcher.cs | 28 +- .../DotNetObjectReferenceJsonConverter.cs | 12 +- ...NetObjectReferenceJsonConverterFactory.cs} | 4 +- .../DotNetObjectReferenceManager.cs} | 20 +- .../IDotNetObjectReference.cs} | 4 +- .../{ => Infrastructure}/TaskGenericsUtil.cs | 2 +- ...ssRuntimeBase.cs => JSInProcessRuntime.cs} | 2 +- .../src/JSInProcessRuntimeExtensions.cs | 29 ++ .../src/JSInvokableAttribute.cs | 2 +- .../Microsoft.JSInterop/src/JSRuntime.cs | 169 +++++++- .../Microsoft.JSInterop/src/JSRuntimeBase.cs | 174 -------- .../src/JSRuntimeExtensions.cs | 140 +++++++ ...efTest.cs => DotNetObjectReferenceTest.cs} | 6 +- .../DotNetDispatcherTest.cs | 78 ++-- .../DotNetObjectReferenceJsonConverterTest.cs | 34 +- .../test/JSInProcessRuntimeExtensionsTest.cs | 27 ++ ...eBaseTest.cs => JSInProcessRuntimeTest.cs} | 18 +- .../test/JSRuntimeBaseTest.cs | 386 ------------------ .../test/JSRuntimeExtensionsTest.cs | 181 ++++++++ .../Microsoft.JSInterop/test/JSRuntimeTest.cs | 380 ++++++++++++++++- .../Microsoft.JSInterop/test/TestJSRuntime.cs | 4 +- ...Mono.WebAssembly.Interop.netstandard2.0.cs | 2 +- .../src/MonoWebAssemblyJSRuntime.cs | 7 +- 28 files changed, 1133 insertions(+), 748 deletions(-) rename src/JSInterop/Microsoft.JSInterop/src/{DotNetObjectRef.cs => DotNetObjectReference.cs} (54%) rename src/JSInterop/Microsoft.JSInterop/src/{DotNetObjectRefOfT.cs => DotNetObjectReferenceOfT.cs} (80%) rename src/JSInterop/Microsoft.JSInterop/src/{ => Infrastructure}/DotNetDispatcher.cs (94%) rename src/JSInterop/Microsoft.JSInterop/src/{ => Infrastructure}/DotNetObjectReferenceJsonConverter.cs (77%) rename src/JSInterop/Microsoft.JSInterop/src/{DotNetObjectRefJsonConverterFactory.cs => Infrastructure/DotNetObjectReferenceJsonConverterFactory.cs} (90%) rename src/JSInterop/Microsoft.JSInterop/src/{DotNetObjectRefManager.cs => Infrastructure/DotNetObjectReferenceManager.cs} (74%) rename src/JSInterop/Microsoft.JSInterop/src/{IDotNetObjectRef.cs => Infrastructure/IDotNetObjectReference.cs} (68%) rename src/JSInterop/Microsoft.JSInterop/src/{ => Infrastructure}/TaskGenericsUtil.cs (98%) rename src/JSInterop/Microsoft.JSInterop/src/{JSInProcessRuntimeBase.cs => JSInProcessRuntime.cs} (95%) create mode 100644 src/JSInterop/Microsoft.JSInterop/src/JSInProcessRuntimeExtensions.cs delete mode 100644 src/JSInterop/Microsoft.JSInterop/src/JSRuntimeBase.cs create mode 100644 src/JSInterop/Microsoft.JSInterop/src/JSRuntimeExtensions.cs rename src/JSInterop/Microsoft.JSInterop/test/{DotNetObjectRefTest.cs => DotNetObjectReferenceTest.cs} (83%) rename src/JSInterop/Microsoft.JSInterop/test/{ => Infrastructure}/DotNetDispatcherTest.cs (91%) rename src/JSInterop/Microsoft.JSInterop/test/{ => Infrastructure}/DotNetObjectReferenceJsonConverterTest.cs (80%) create mode 100644 src/JSInterop/Microsoft.JSInterop/test/JSInProcessRuntimeExtensionsTest.cs rename src/JSInterop/Microsoft.JSInterop/test/{JSInProcessRuntimeBaseTest.cs => JSInProcessRuntimeTest.cs} (88%) delete mode 100644 src/JSInterop/Microsoft.JSInterop/test/JSRuntimeBaseTest.cs create mode 100644 src/JSInterop/Microsoft.JSInterop/test/JSRuntimeExtensionsTest.cs diff --git a/src/JSInterop/Microsoft.JSInterop/ref/Microsoft.JSInterop.netcoreapp3.0.cs b/src/JSInterop/Microsoft.JSInterop/ref/Microsoft.JSInterop.netcoreapp3.0.cs index e73fa1be69..a5fbbc768a 100644 --- a/src/JSInterop/Microsoft.JSInterop/ref/Microsoft.JSInterop.netcoreapp3.0.cs +++ b/src/JSInterop/Microsoft.JSInterop/ref/Microsoft.JSInterop.netcoreapp3.0.cs @@ -3,19 +3,13 @@ namespace Microsoft.JSInterop { - public static partial class DotNetDispatcher + public static partial class DotNetObjectReference { - public static void BeginInvoke(string callId, string assemblyName, string methodIdentifier, long dotNetObjectId, string argsJson) { } - public static void EndInvoke(string arguments) { } - public static string Invoke(string assemblyName, string methodIdentifier, long dotNetObjectId, string argsJson) { throw null; } + public static Microsoft.JSInterop.DotNetObjectReference Create(TValue value) where TValue : class { throw null; } } - public static partial class DotNetObjectRef + public sealed partial class DotNetObjectReference : System.IDisposable where TValue : class { - public static Microsoft.JSInterop.DotNetObjectRef Create(TValue value) where TValue : class { throw null; } - } - public sealed partial class DotNetObjectRef : System.IDisposable where TValue : class - { - internal DotNetObjectRef() { } + internal DotNetObjectReference() { } public TValue Value { get { throw null; } } public void Dispose() { } } @@ -25,38 +19,61 @@ namespace Microsoft.JSInterop } public partial interface IJSRuntime { - System.Threading.Tasks.Task InvokeAsync(string identifier, System.Collections.Generic.IEnumerable args, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)); - System.Threading.Tasks.Task InvokeAsync(string identifier, params object[] args); + System.Threading.Tasks.ValueTask InvokeAsync(string identifier, object[] args); + System.Threading.Tasks.ValueTask InvokeAsync(string identifier, System.Threading.CancellationToken cancellationToken, object[] args); } public partial class JSException : System.Exception { public JSException(string message) { } public JSException(string message, System.Exception innerException) { } } - public abstract partial class JSInProcessRuntimeBase : Microsoft.JSInterop.JSRuntimeBase, Microsoft.JSInterop.IJSInProcessRuntime, Microsoft.JSInterop.IJSRuntime + public abstract partial class JSInProcessRuntime : Microsoft.JSInterop.JSRuntime, Microsoft.JSInterop.IJSInProcessRuntime, Microsoft.JSInterop.IJSRuntime { - protected JSInProcessRuntimeBase() { } + protected JSInProcessRuntime() { } protected abstract string InvokeJS(string identifier, string argsJson); public TValue Invoke(string identifier, params object[] args) { throw null; } } + public static partial class JSInProcessRuntimeExtensions + { + public static void InvokeVoid(this Microsoft.JSInterop.IJSInProcessRuntime jsRuntime, string identifier, params object[] args) { } + } [System.AttributeUsageAttribute(System.AttributeTargets.Method, AllowMultiple=true)] - public partial class JSInvokableAttribute : System.Attribute + public sealed partial class JSInvokableAttribute : System.Attribute { public JSInvokableAttribute() { } public JSInvokableAttribute(string identifier) { } public string Identifier { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } } } - public static partial class JSRuntime + public abstract partial class JSRuntime : Microsoft.JSInterop.IJSRuntime { - public static void SetCurrentJSRuntime(Microsoft.JSInterop.IJSRuntime instance) { } - } - public abstract partial class JSRuntimeBase : Microsoft.JSInterop.IJSRuntime - { - protected JSRuntimeBase() { } + protected JSRuntime() { } protected System.TimeSpan? DefaultAsyncTimeout { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } protected abstract void BeginInvokeJS(long taskId, string identifier, string argsJson); protected internal abstract void EndInvokeDotNet(string callId, bool success, object resultOrError, string assemblyName, string methodIdentifier, long dotNetObjectId); - public System.Threading.Tasks.Task InvokeAsync(string identifier, System.Collections.Generic.IEnumerable args, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) { throw null; } - public System.Threading.Tasks.Task InvokeAsync(string identifier, params object[] args) { throw null; } + public System.Threading.Tasks.ValueTask InvokeAsync(string identifier, object[] args) { throw null; } + public System.Threading.Tasks.ValueTask InvokeAsync(string identifier, System.Threading.CancellationToken cancellationToken, object[] args) { throw null; } + public static void SetCurrentJSRuntime(Microsoft.JSInterop.IJSRuntime instance) { } + } + public static partial class JSRuntimeExtensions + { + public static System.Threading.Tasks.ValueTask InvokeAsync(this Microsoft.JSInterop.IJSRuntime jsRuntime, string identifier, params object[] args) { throw null; } + public static System.Threading.Tasks.ValueTask InvokeAsync(this Microsoft.JSInterop.IJSRuntime jsRuntime, string identifier, System.Threading.CancellationToken cancellationToken, params object[] args) { throw null; } + [System.Diagnostics.DebuggerStepThroughAttribute] + public static System.Threading.Tasks.ValueTask InvokeAsync(this Microsoft.JSInterop.IJSRuntime jsRuntime, string identifier, System.TimeSpan timeout, params object[] args) { throw null; } + [System.Diagnostics.DebuggerStepThroughAttribute] + public static System.Threading.Tasks.ValueTask InvokeVoidAsync(this Microsoft.JSInterop.IJSRuntime jsRuntime, string identifier, params object[] args) { throw null; } + [System.Diagnostics.DebuggerStepThroughAttribute] + public static System.Threading.Tasks.ValueTask InvokeVoidAsync(this Microsoft.JSInterop.IJSRuntime jsRuntime, string identifier, System.Threading.CancellationToken cancellationToken, params object[] args) { throw null; } + [System.Diagnostics.DebuggerStepThroughAttribute] + public static System.Threading.Tasks.ValueTask InvokeVoidAsync(this Microsoft.JSInterop.IJSRuntime jsRuntime, string identifier, System.TimeSpan timeout, params object[] args) { throw null; } + } +} +namespace Microsoft.JSInterop.Infrastructure +{ + public static partial class DotNetDispatcher + { + public static void BeginInvokeDotNet(string callId, string assemblyName, string methodIdentifier, long dotNetObjectId, string argsJson) { } + public static void EndInvokeJS(string arguments) { } + public static string Invoke(string assemblyName, string methodIdentifier, long dotNetObjectId, string argsJson) { throw null; } } } diff --git a/src/JSInterop/Microsoft.JSInterop/ref/Microsoft.JSInterop.netstandard2.0.cs b/src/JSInterop/Microsoft.JSInterop/ref/Microsoft.JSInterop.netstandard2.0.cs index e73fa1be69..a5fbbc768a 100644 --- a/src/JSInterop/Microsoft.JSInterop/ref/Microsoft.JSInterop.netstandard2.0.cs +++ b/src/JSInterop/Microsoft.JSInterop/ref/Microsoft.JSInterop.netstandard2.0.cs @@ -3,19 +3,13 @@ namespace Microsoft.JSInterop { - public static partial class DotNetDispatcher + public static partial class DotNetObjectReference { - public static void BeginInvoke(string callId, string assemblyName, string methodIdentifier, long dotNetObjectId, string argsJson) { } - public static void EndInvoke(string arguments) { } - public static string Invoke(string assemblyName, string methodIdentifier, long dotNetObjectId, string argsJson) { throw null; } + public static Microsoft.JSInterop.DotNetObjectReference Create(TValue value) where TValue : class { throw null; } } - public static partial class DotNetObjectRef + public sealed partial class DotNetObjectReference : System.IDisposable where TValue : class { - public static Microsoft.JSInterop.DotNetObjectRef Create(TValue value) where TValue : class { throw null; } - } - public sealed partial class DotNetObjectRef : System.IDisposable where TValue : class - { - internal DotNetObjectRef() { } + internal DotNetObjectReference() { } public TValue Value { get { throw null; } } public void Dispose() { } } @@ -25,38 +19,61 @@ namespace Microsoft.JSInterop } public partial interface IJSRuntime { - System.Threading.Tasks.Task InvokeAsync(string identifier, System.Collections.Generic.IEnumerable args, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)); - System.Threading.Tasks.Task InvokeAsync(string identifier, params object[] args); + System.Threading.Tasks.ValueTask InvokeAsync(string identifier, object[] args); + System.Threading.Tasks.ValueTask InvokeAsync(string identifier, System.Threading.CancellationToken cancellationToken, object[] args); } public partial class JSException : System.Exception { public JSException(string message) { } public JSException(string message, System.Exception innerException) { } } - public abstract partial class JSInProcessRuntimeBase : Microsoft.JSInterop.JSRuntimeBase, Microsoft.JSInterop.IJSInProcessRuntime, Microsoft.JSInterop.IJSRuntime + public abstract partial class JSInProcessRuntime : Microsoft.JSInterop.JSRuntime, Microsoft.JSInterop.IJSInProcessRuntime, Microsoft.JSInterop.IJSRuntime { - protected JSInProcessRuntimeBase() { } + protected JSInProcessRuntime() { } protected abstract string InvokeJS(string identifier, string argsJson); public TValue Invoke(string identifier, params object[] args) { throw null; } } + public static partial class JSInProcessRuntimeExtensions + { + public static void InvokeVoid(this Microsoft.JSInterop.IJSInProcessRuntime jsRuntime, string identifier, params object[] args) { } + } [System.AttributeUsageAttribute(System.AttributeTargets.Method, AllowMultiple=true)] - public partial class JSInvokableAttribute : System.Attribute + public sealed partial class JSInvokableAttribute : System.Attribute { public JSInvokableAttribute() { } public JSInvokableAttribute(string identifier) { } public string Identifier { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } } } - public static partial class JSRuntime + public abstract partial class JSRuntime : Microsoft.JSInterop.IJSRuntime { - public static void SetCurrentJSRuntime(Microsoft.JSInterop.IJSRuntime instance) { } - } - public abstract partial class JSRuntimeBase : Microsoft.JSInterop.IJSRuntime - { - protected JSRuntimeBase() { } + protected JSRuntime() { } protected System.TimeSpan? DefaultAsyncTimeout { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } protected abstract void BeginInvokeJS(long taskId, string identifier, string argsJson); protected internal abstract void EndInvokeDotNet(string callId, bool success, object resultOrError, string assemblyName, string methodIdentifier, long dotNetObjectId); - public System.Threading.Tasks.Task InvokeAsync(string identifier, System.Collections.Generic.IEnumerable args, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) { throw null; } - public System.Threading.Tasks.Task InvokeAsync(string identifier, params object[] args) { throw null; } + public System.Threading.Tasks.ValueTask InvokeAsync(string identifier, object[] args) { throw null; } + public System.Threading.Tasks.ValueTask InvokeAsync(string identifier, System.Threading.CancellationToken cancellationToken, object[] args) { throw null; } + public static void SetCurrentJSRuntime(Microsoft.JSInterop.IJSRuntime instance) { } + } + public static partial class JSRuntimeExtensions + { + public static System.Threading.Tasks.ValueTask InvokeAsync(this Microsoft.JSInterop.IJSRuntime jsRuntime, string identifier, params object[] args) { throw null; } + public static System.Threading.Tasks.ValueTask InvokeAsync(this Microsoft.JSInterop.IJSRuntime jsRuntime, string identifier, System.Threading.CancellationToken cancellationToken, params object[] args) { throw null; } + [System.Diagnostics.DebuggerStepThroughAttribute] + public static System.Threading.Tasks.ValueTask InvokeAsync(this Microsoft.JSInterop.IJSRuntime jsRuntime, string identifier, System.TimeSpan timeout, params object[] args) { throw null; } + [System.Diagnostics.DebuggerStepThroughAttribute] + public static System.Threading.Tasks.ValueTask InvokeVoidAsync(this Microsoft.JSInterop.IJSRuntime jsRuntime, string identifier, params object[] args) { throw null; } + [System.Diagnostics.DebuggerStepThroughAttribute] + public static System.Threading.Tasks.ValueTask InvokeVoidAsync(this Microsoft.JSInterop.IJSRuntime jsRuntime, string identifier, System.Threading.CancellationToken cancellationToken, params object[] args) { throw null; } + [System.Diagnostics.DebuggerStepThroughAttribute] + public static System.Threading.Tasks.ValueTask InvokeVoidAsync(this Microsoft.JSInterop.IJSRuntime jsRuntime, string identifier, System.TimeSpan timeout, params object[] args) { throw null; } + } +} +namespace Microsoft.JSInterop.Infrastructure +{ + public static partial class DotNetDispatcher + { + public static void BeginInvokeDotNet(string callId, string assemblyName, string methodIdentifier, long dotNetObjectId, string argsJson) { } + public static void EndInvokeJS(string arguments) { } + public static string Invoke(string assemblyName, string methodIdentifier, long dotNetObjectId, string argsJson) { throw null; } } } diff --git a/src/JSInterop/Microsoft.JSInterop/src/DotNetObjectRef.cs b/src/JSInterop/Microsoft.JSInterop/src/DotNetObjectReference.cs similarity index 54% rename from src/JSInterop/Microsoft.JSInterop/src/DotNetObjectRef.cs rename to src/JSInterop/Microsoft.JSInterop/src/DotNetObjectReference.cs index f604bab272..24b13f0c85 100644 --- a/src/JSInterop/Microsoft.JSInterop/src/DotNetObjectRef.cs +++ b/src/JSInterop/Microsoft.JSInterop/src/DotNetObjectReference.cs @@ -1,21 +1,23 @@ // 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 Microsoft.JSInterop.Infrastructure; + namespace Microsoft.JSInterop { /// - /// Provides convenience methods to produce a . + /// Provides convenience methods to produce a . /// - public static class DotNetObjectRef + public static class DotNetObjectReference { /// - /// Creates a new instance of . + /// Creates a new instance of . /// /// The reference type to track. - /// An instance of . - public static DotNetObjectRef Create(TValue value) where TValue : class + /// An instance of . + public static DotNetObjectReference Create(TValue value) where TValue : class { - return new DotNetObjectRef(DotNetObjectRefManager.Current, value); + return new DotNetObjectReference(DotNetObjectReferenceManager.Current, value); } } } diff --git a/src/JSInterop/Microsoft.JSInterop/src/DotNetObjectRefOfT.cs b/src/JSInterop/Microsoft.JSInterop/src/DotNetObjectReferenceOfT.cs similarity index 80% rename from src/JSInterop/Microsoft.JSInterop/src/DotNetObjectRefOfT.cs rename to src/JSInterop/Microsoft.JSInterop/src/DotNetObjectReferenceOfT.cs index d83d0e89bb..eb1ac6a234 100644 --- a/src/JSInterop/Microsoft.JSInterop/src/DotNetObjectRefOfT.cs +++ b/src/JSInterop/Microsoft.JSInterop/src/DotNetObjectReferenceOfT.cs @@ -3,6 +3,7 @@ using System; using System.Text.Json.Serialization; +using Microsoft.JSInterop.Infrastructure; namespace Microsoft.JSInterop { @@ -14,31 +15,24 @@ namespace Microsoft.JSInterop /// /// The type of the value to wrap. [JsonConverter(typeof(DotNetObjectReferenceJsonConverterFactory))] - public sealed class DotNetObjectRef : IDotNetObjectRef, IDisposable where TValue : class + public sealed class DotNetObjectReference : IDotNetObjectReference, IDisposable where TValue : class { - private readonly DotNetObjectRefManager _referenceManager; + private readonly DotNetObjectReferenceManager _referenceManager; private readonly TValue _value; private readonly long _objectId; /// - /// Initializes a new instance of . + /// Initializes a new instance of . /// /// /// The value to pass by reference. - internal DotNetObjectRef(DotNetObjectRefManager referenceManager, TValue value) + internal DotNetObjectReference(DotNetObjectReferenceManager referenceManager, TValue value) { _referenceManager = referenceManager; _objectId = _referenceManager.TrackObject(this); _value = value; } - internal DotNetObjectRef(DotNetObjectRefManager referenceManager, long objectId, TValue value) - { - _referenceManager = referenceManager; - _objectId = objectId; - _value = value; - } - /// /// Gets the object instance represented by this wrapper. /// @@ -60,7 +54,7 @@ namespace Microsoft.JSInterop } } - object IDotNetObjectRef.Value => Value; + object IDotNetObjectReference.Value => Value; internal bool Disposed { get; private set; } diff --git a/src/JSInterop/Microsoft.JSInterop/src/IJSRuntime.cs b/src/JSInterop/Microsoft.JSInterop/src/IJSRuntime.cs index d7ee372a73..6edc725177 100644 --- a/src/JSInterop/Microsoft.JSInterop/src/IJSRuntime.cs +++ b/src/JSInterop/Microsoft.JSInterop/src/IJSRuntime.cs @@ -1,7 +1,6 @@ // 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.Collections.Generic; using System.Threading; using System.Threading.Tasks; @@ -14,21 +13,28 @@ namespace Microsoft.JSInterop { /// /// Invokes the specified JavaScript function asynchronously. + /// + /// will apply timeouts to this operation based on the value configured in . To dispatch a call with a different timeout, or no timeout, + /// consider using . + /// /// /// The JSON-serializable return type. /// An identifier for the function to invoke. For example, the value "someScope.someFunction" will invoke the function window.someScope.someFunction. /// JSON-serializable arguments. /// An instance of obtained by JSON-deserializing the return value. - Task InvokeAsync(string identifier, params object[] args); + ValueTask InvokeAsync(string identifier, object[] args); /// /// Invokes the specified JavaScript function asynchronously. /// /// The JSON-serializable return type. /// An identifier for the function to invoke. For example, the value "someScope.someFunction" will invoke the function window.someScope.someFunction. + /// + /// A cancellation token to signal the cancellation of the operation. Specifying this parameter will override any default cancellations such as due to timeouts + /// () from being applied. + /// /// JSON-serializable arguments. - /// A cancellation token to signal the cancellation of the operation. /// An instance of obtained by JSON-deserializing the return value. - Task InvokeAsync(string identifier, IEnumerable args, CancellationToken cancellationToken = default); + ValueTask InvokeAsync(string identifier, CancellationToken cancellationToken, object[] args); } } diff --git a/src/JSInterop/Microsoft.JSInterop/src/DotNetDispatcher.cs b/src/JSInterop/Microsoft.JSInterop/src/Infrastructure/DotNetDispatcher.cs similarity index 94% rename from src/JSInterop/Microsoft.JSInterop/src/DotNetDispatcher.cs rename to src/JSInterop/Microsoft.JSInterop/src/Infrastructure/DotNetDispatcher.cs index e639a33ff2..d4a4de14dd 100644 --- a/src/JSInterop/Microsoft.JSInterop/src/DotNetDispatcher.cs +++ b/src/JSInterop/Microsoft.JSInterop/src/Infrastructure/DotNetDispatcher.cs @@ -11,7 +11,7 @@ using System.Text; using System.Text.Json; using System.Threading.Tasks; -namespace Microsoft.JSInterop +namespace Microsoft.JSInterop.Infrastructure { /// /// Provides methods that receive incoming calls from JS to .NET. @@ -39,10 +39,10 @@ namespace Microsoft.JSInterop // the targeted method has [JSInvokable]. It is not itself subject to that restriction, // because there would be nobody to police that. This method *is* the police. - IDotNetObjectRef targetInstance = default; + IDotNetObjectReference targetInstance = default; if (dotNetObjectId != default) { - targetInstance = DotNetObjectRefManager.Current.FindDotNetObject(dotNetObjectId); + targetInstance = DotNetObjectReferenceManager.Current.FindDotNetObject(dotNetObjectId); } var syncResult = InvokeSynchronously(assemblyName, methodIdentifier, targetInstance, argsJson); @@ -63,7 +63,7 @@ namespace Microsoft.JSInterop /// For instance method calls, identifies the target object. /// A JSON representation of the parameters. /// A JSON representation of the return value, or null. - public static void BeginInvoke(string callId, string assemblyName, string methodIdentifier, long dotNetObjectId, string argsJson) + public static void BeginInvokeDotNet(string callId, string assemblyName, string methodIdentifier, long dotNetObjectId, string argsJson) { // This method doesn't need [JSInvokable] because the platform is responsible for having // some way to dispatch calls here. The logic inside here is the thing that checks whether @@ -73,19 +73,19 @@ namespace Microsoft.JSInterop // DotNetDispatcher only works with JSRuntimeBase instances. // If the developer wants to use a totally custom IJSRuntime, then their JS-side // code has to implement its own way of returning async results. - var jsRuntimeBaseInstance = (JSRuntimeBase)JSRuntime.Current; + var jsRuntimeBaseInstance = (JSRuntime)JSRuntime.Current; // Using ExceptionDispatchInfo here throughout because we want to always preserve // original stack traces. object syncResult = null; ExceptionDispatchInfo syncException = null; - IDotNetObjectRef targetInstance = null; + IDotNetObjectReference targetInstance = null; try { if (dotNetObjectId != default) { - targetInstance = DotNetObjectRefManager.Current.FindDotNetObject(dotNetObjectId); + targetInstance = DotNetObjectReferenceManager.Current.FindDotNetObject(dotNetObjectId); } syncResult = InvokeSynchronously(assemblyName, methodIdentifier, targetInstance, argsJson); @@ -128,7 +128,7 @@ namespace Microsoft.JSInterop } } - private static object InvokeSynchronously(string assemblyName, string methodIdentifier, IDotNetObjectRef objectReference, string argsJson) + private static object InvokeSynchronously(string assemblyName, string methodIdentifier, IDotNetObjectReference objectReference, string argsJson) { AssemblyKey assemblyKey; if (objectReference is null) @@ -227,7 +227,7 @@ namespace Microsoft.JSInterop jsonReader.ValueTextEquals(DotNetObjectRefKey.EncodedUtf8Bytes)) { // The JSON payload has the shape we expect from a DotNetObjectRef instance. - return !parameterType.IsGenericType || parameterType.GetGenericTypeDefinition() != typeof(DotNetObjectRef<>); + return !parameterType.IsGenericType || parameterType.GetGenericTypeDefinition() != typeof(DotNetObjectReference<>); } return false; @@ -239,9 +239,9 @@ namespace Microsoft.JSInterop /// associated as completed. /// /// - /// All exceptions from are caught + /// All exceptions from are caught /// are delivered via JS interop to the JavaScript side when it requests confirmation, as - /// the mechanism to call relies on + /// the mechanism to call relies on /// using JS->.NET interop. This overload is meant for directly triggering completion callbacks /// for .NET -> JS operations without going through JS interop, so the callsite for this /// method is responsible for handling any possible exception generated from the arguments @@ -252,13 +252,13 @@ namespace Microsoft.JSInterop /// This method can throw any exception either from the argument received or as a result /// of executing any callback synchronously upon completion. /// - public static void EndInvoke(string arguments) + public static void EndInvokeJS(string arguments) { - var jsRuntimeBase = (JSRuntimeBase)JSRuntime.Current; + var jsRuntimeBase = (JSRuntime)JSRuntime.Current; ParseEndInvokeArguments(jsRuntimeBase, arguments); } - internal static void ParseEndInvokeArguments(JSRuntimeBase jsRuntimeBase, string arguments) + internal static void ParseEndInvokeArguments(JSRuntime jsRuntimeBase, string arguments) { var utf8JsonBytes = Encoding.UTF8.GetBytes(arguments); diff --git a/src/JSInterop/Microsoft.JSInterop/src/DotNetObjectReferenceJsonConverter.cs b/src/JSInterop/Microsoft.JSInterop/src/Infrastructure/DotNetObjectReferenceJsonConverter.cs similarity index 77% rename from src/JSInterop/Microsoft.JSInterop/src/DotNetObjectReferenceJsonConverter.cs rename to src/JSInterop/Microsoft.JSInterop/src/Infrastructure/DotNetObjectReferenceJsonConverter.cs index 71bfa28ad5..c077ac0b17 100644 --- a/src/JSInterop/Microsoft.JSInterop/src/DotNetObjectReferenceJsonConverter.cs +++ b/src/JSInterop/Microsoft.JSInterop/src/Infrastructure/DotNetObjectReferenceJsonConverter.cs @@ -5,13 +5,13 @@ using System; using System.Text.Json; using System.Text.Json.Serialization; -namespace Microsoft.JSInterop +namespace Microsoft.JSInterop.Infrastructure { - internal sealed class DotNetObjectReferenceJsonConverter : JsonConverter> where TValue : class + internal sealed class DotNetObjectReferenceJsonConverter : JsonConverter> where TValue : class { private static JsonEncodedText DotNetObjectRefKey => DotNetDispatcher.DotNetObjectRefKey; - public override DotNetObjectRef Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + public override DotNetObjectReference Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) { long dotNetObjectId = 0; @@ -40,11 +40,11 @@ namespace Microsoft.JSInterop throw new JsonException($"Required property {DotNetObjectRefKey} not found."); } - var referenceManager = DotNetObjectRefManager.Current; - return (DotNetObjectRef)referenceManager.FindDotNetObject(dotNetObjectId); + var referenceManager = DotNetObjectReferenceManager.Current; + return (DotNetObjectReference)referenceManager.FindDotNetObject(dotNetObjectId); } - public override void Write(Utf8JsonWriter writer, DotNetObjectRef value, JsonSerializerOptions options) + public override void Write(Utf8JsonWriter writer, DotNetObjectReference value, JsonSerializerOptions options) { writer.WriteStartObject(); writer.WriteNumber(DotNetObjectRefKey, value.ObjectId); diff --git a/src/JSInterop/Microsoft.JSInterop/src/DotNetObjectRefJsonConverterFactory.cs b/src/JSInterop/Microsoft.JSInterop/src/Infrastructure/DotNetObjectReferenceJsonConverterFactory.cs similarity index 90% rename from src/JSInterop/Microsoft.JSInterop/src/DotNetObjectRefJsonConverterFactory.cs rename to src/JSInterop/Microsoft.JSInterop/src/Infrastructure/DotNetObjectReferenceJsonConverterFactory.cs index 5cfec5be9d..350530b624 100644 --- a/src/JSInterop/Microsoft.JSInterop/src/DotNetObjectRefJsonConverterFactory.cs +++ b/src/JSInterop/Microsoft.JSInterop/src/Infrastructure/DotNetObjectReferenceJsonConverterFactory.cs @@ -5,13 +5,13 @@ using System; using System.Text.Json; using System.Text.Json.Serialization; -namespace Microsoft.JSInterop +namespace Microsoft.JSInterop.Infrastructure { internal sealed class DotNetObjectReferenceJsonConverterFactory : JsonConverterFactory { public override bool CanConvert(Type typeToConvert) { - return typeToConvert.IsGenericType && typeToConvert.GetGenericTypeDefinition() == typeof(DotNetObjectRef<>); + return typeToConvert.IsGenericType && typeToConvert.GetGenericTypeDefinition() == typeof(DotNetObjectReference<>); } public override JsonConverter CreateConverter(Type typeToConvert, JsonSerializerOptions jsonSerializerOptions) diff --git a/src/JSInterop/Microsoft.JSInterop/src/DotNetObjectRefManager.cs b/src/JSInterop/Microsoft.JSInterop/src/Infrastructure/DotNetObjectReferenceManager.cs similarity index 74% rename from src/JSInterop/Microsoft.JSInterop/src/DotNetObjectRefManager.cs rename to src/JSInterop/Microsoft.JSInterop/src/Infrastructure/DotNetObjectReferenceManager.cs index a6be6aeb4f..709dd963fa 100644 --- a/src/JSInterop/Microsoft.JSInterop/src/DotNetObjectRefManager.cs +++ b/src/JSInterop/Microsoft.JSInterop/src/Infrastructure/DotNetObjectReferenceManager.cs @@ -5,27 +5,27 @@ using System; using System.Collections.Concurrent; using System.Threading; -namespace Microsoft.JSInterop +namespace Microsoft.JSInterop.Infrastructure { - internal class DotNetObjectRefManager + internal class DotNetObjectReferenceManager { private long _nextId = 0; // 0 signals no object, but we increment prior to assignment. The first tracked object should have id 1 - private readonly ConcurrentDictionary _trackedRefsById = new ConcurrentDictionary(); + private readonly ConcurrentDictionary _trackedRefsById = new ConcurrentDictionary(); - public static DotNetObjectRefManager Current + public static DotNetObjectReferenceManager Current { get { - if (!(JSRuntime.Current is JSRuntimeBase jsRuntimeBase)) + if (!(JSRuntime.Current is JSRuntime jsRuntime)) { - throw new InvalidOperationException("JSRuntime must be set up correctly and must be an instance of JSRuntimeBase to use DotNetObjectRef."); + throw new InvalidOperationException("JSRuntime must be set up correctly and must be an instance of JSRuntimeBase to use DotNetObjectReference."); } - return jsRuntimeBase.ObjectRefManager; + return jsRuntime.ObjectRefManager; } } - public long TrackObject(IDotNetObjectRef dotNetObjectRef) + public long TrackObject(IDotNetObjectReference dotNetObjectRef) { var dotNetObjectId = Interlocked.Increment(ref _nextId); _trackedRefsById[dotNetObjectId] = dotNetObjectRef; @@ -33,7 +33,7 @@ namespace Microsoft.JSInterop return dotNetObjectId; } - public IDotNetObjectRef FindDotNetObject(long dotNetObjectId) + public IDotNetObjectReference FindDotNetObject(long dotNetObjectId) { return _trackedRefsById.TryGetValue(dotNetObjectId, out var dotNetObjectRef) ? dotNetObjectRef @@ -45,7 +45,7 @@ namespace Microsoft.JSInterop /// Stops tracking the specified .NET object reference. /// This may be invoked either by disposing a DotNetObjectRef in .NET code, or via JS interop by calling "dispose" on the corresponding instance in JavaScript code /// - /// The ID of the . + /// The ID of the . public void ReleaseDotNetObject(long dotNetObjectId) => _trackedRefsById.TryRemove(dotNetObjectId, out _); } } diff --git a/src/JSInterop/Microsoft.JSInterop/src/IDotNetObjectRef.cs b/src/JSInterop/Microsoft.JSInterop/src/Infrastructure/IDotNetObjectReference.cs similarity index 68% rename from src/JSInterop/Microsoft.JSInterop/src/IDotNetObjectRef.cs rename to src/JSInterop/Microsoft.JSInterop/src/Infrastructure/IDotNetObjectReference.cs index da16fa60a0..4b84f2bd0c 100644 --- a/src/JSInterop/Microsoft.JSInterop/src/IDotNetObjectRef.cs +++ b/src/JSInterop/Microsoft.JSInterop/src/Infrastructure/IDotNetObjectReference.cs @@ -3,9 +3,9 @@ using System; -namespace Microsoft.JSInterop +namespace Microsoft.JSInterop.Infrastructure { - internal interface IDotNetObjectRef : IDisposable + internal interface IDotNetObjectReference : IDisposable { object Value { get; } } diff --git a/src/JSInterop/Microsoft.JSInterop/src/TaskGenericsUtil.cs b/src/JSInterop/Microsoft.JSInterop/src/Infrastructure/TaskGenericsUtil.cs similarity index 98% rename from src/JSInterop/Microsoft.JSInterop/src/TaskGenericsUtil.cs rename to src/JSInterop/Microsoft.JSInterop/src/Infrastructure/TaskGenericsUtil.cs index 734e9863b8..4e14d50783 100644 --- a/src/JSInterop/Microsoft.JSInterop/src/TaskGenericsUtil.cs +++ b/src/JSInterop/Microsoft.JSInterop/src/Infrastructure/TaskGenericsUtil.cs @@ -6,7 +6,7 @@ using System.Collections.Concurrent; using System.Linq; using System.Threading.Tasks; -namespace Microsoft.JSInterop +namespace Microsoft.JSInterop.Infrastructure { internal static class TaskGenericsUtil { diff --git a/src/JSInterop/Microsoft.JSInterop/src/JSInProcessRuntimeBase.cs b/src/JSInterop/Microsoft.JSInterop/src/JSInProcessRuntime.cs similarity index 95% rename from src/JSInterop/Microsoft.JSInterop/src/JSInProcessRuntimeBase.cs rename to src/JSInterop/Microsoft.JSInterop/src/JSInProcessRuntime.cs index 7606f1865f..cf8cc7030b 100644 --- a/src/JSInterop/Microsoft.JSInterop/src/JSInProcessRuntimeBase.cs +++ b/src/JSInterop/Microsoft.JSInterop/src/JSInProcessRuntime.cs @@ -8,7 +8,7 @@ namespace Microsoft.JSInterop /// /// Abstract base class for an in-process JavaScript runtime. /// - public abstract class JSInProcessRuntimeBase : JSRuntimeBase, IJSInProcessRuntime + public abstract class JSInProcessRuntime : JSRuntime, IJSInProcessRuntime { /// /// Invokes the specified JavaScript function synchronously. diff --git a/src/JSInterop/Microsoft.JSInterop/src/JSInProcessRuntimeExtensions.cs b/src/JSInterop/Microsoft.JSInterop/src/JSInProcessRuntimeExtensions.cs new file mode 100644 index 0000000000..73bc247848 --- /dev/null +++ b/src/JSInterop/Microsoft.JSInterop/src/JSInProcessRuntimeExtensions.cs @@ -0,0 +1,29 @@ +// 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; + +namespace Microsoft.JSInterop +{ + /// + /// Extensions for . + /// + public static class JSInProcessRuntimeExtensions + { + /// + /// Invokes the specified JavaScript function synchronously. + /// + /// The . + /// An identifier for the function to invoke. For example, the value "someScope.someFunction" will invoke the function window.someScope.someFunction. + /// JSON-serializable arguments. + public static void InvokeVoid(this IJSInProcessRuntime jsRuntime, string identifier, params object[] args) + { + if (jsRuntime == null) + { + throw new ArgumentNullException(nameof(jsRuntime)); + } + + jsRuntime.Invoke(identifier, args); + } + } +} diff --git a/src/JSInterop/Microsoft.JSInterop/src/JSInvokableAttribute.cs b/src/JSInterop/Microsoft.JSInterop/src/JSInvokableAttribute.cs index e037078cba..b710d54b2c 100644 --- a/src/JSInterop/Microsoft.JSInterop/src/JSInvokableAttribute.cs +++ b/src/JSInterop/Microsoft.JSInterop/src/JSInvokableAttribute.cs @@ -11,7 +11,7 @@ namespace Microsoft.JSInterop /// from untrusted callers. All inputs should be validated carefully. /// [AttributeUsage(AttributeTargets.Method, AllowMultiple = true)] - public class JSInvokableAttribute : Attribute + public sealed class JSInvokableAttribute : Attribute { /// /// Gets the identifier for the method. The identifier must be unique within the scope diff --git a/src/JSInterop/Microsoft.JSInterop/src/JSRuntime.cs b/src/JSInterop/Microsoft.JSInterop/src/JSRuntime.cs index ae097ca68e..598b47c4d4 100644 --- a/src/JSInterop/Microsoft.JSInterop/src/JSRuntime.cs +++ b/src/JSInterop/Microsoft.JSInterop/src/JSRuntime.cs @@ -2,19 +2,38 @@ // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; +using System.Collections.Concurrent; +using System.Linq; +using System.Text.Json; using System.Threading; +using System.Threading.Tasks; +using Microsoft.JSInterop.Infrastructure; namespace Microsoft.JSInterop { /// - /// Provides mechanisms for accessing the current . + /// Abstract base class for a JavaScript runtime. /// - public static class JSRuntime + public abstract partial class JSRuntime : IJSRuntime { private static readonly AsyncLocal _currentJSRuntime = new AsyncLocal(); internal static IJSRuntime Current => _currentJSRuntime.Value; + private long _nextPendingTaskId = 1; // Start at 1 because zero signals "no response needed" + private readonly ConcurrentDictionary _pendingTasks + = new ConcurrentDictionary(); + + private readonly ConcurrentDictionary _cancellationRegistrations = + new ConcurrentDictionary(); + + internal DotNetObjectReferenceManager ObjectRefManager { get; } = new DotNetObjectReferenceManager(); + + /// + /// Gets or sets the default timeout for asynchronous JavaScript calls. + /// + protected TimeSpan? DefaultAsyncTimeout { get; set; } + /// /// Sets the current JS runtime to the supplied instance. /// @@ -26,5 +45,151 @@ namespace Microsoft.JSInterop _currentJSRuntime.Value = instance ?? throw new ArgumentNullException(nameof(instance)); } + + /// + /// Invokes the specified JavaScript function asynchronously. + /// + /// will apply timeouts to this operation based on the value configured in . To dispatch a call with a different, or no timeout, + /// consider using . + /// + /// + /// The JSON-serializable return type. + /// An identifier for the function to invoke. For example, the value "someScope.someFunction" will invoke the function window.someScope.someFunction. + /// JSON-serializable arguments. + /// An instance of obtained by JSON-deserializing the return value. + public ValueTask InvokeAsync(string identifier, object[] args) + { + if (DefaultAsyncTimeout.HasValue) + { + return InvokeWithDefaultCancellation(identifier, args); + } + + return InvokeAsync(identifier, CancellationToken.None, args); + } + + /// + /// Invokes the specified JavaScript function asynchronously. + /// + /// The JSON-serializable return type. + /// An identifier for the function to invoke. For example, the value "someScope.someFunction" will invoke the function window.someScope.someFunction. + /// + /// A cancellation token to signal the cancellation of the operation. Specifying this parameter will override any default cancellations such as due to timeouts + /// () from being applied. + /// + /// JSON-serializable arguments. + /// An instance of obtained by JSON-deserializing the return value. + public ValueTask InvokeAsync(string identifier, CancellationToken cancellationToken, object[] args) + { + var taskId = Interlocked.Increment(ref _nextPendingTaskId); + var tcs = new TaskCompletionSource(TaskContinuationOptions.RunContinuationsAsynchronously); + if (cancellationToken != default) + { + _cancellationRegistrations[taskId] = cancellationToken.Register(() => + { + tcs.TrySetCanceled(cancellationToken); + CleanupTasksAndRegistrations(taskId); + }); + } + _pendingTasks[taskId] = tcs; + + try + { + if (cancellationToken.IsCancellationRequested) + { + tcs.TrySetCanceled(cancellationToken); + CleanupTasksAndRegistrations(taskId); + + return new ValueTask(tcs.Task); + } + + var argsJson = args?.Any() == true ? + JsonSerializer.Serialize(args, JsonSerializerOptionsProvider.Options) : + null; + BeginInvokeJS(taskId, identifier, argsJson); + + return new ValueTask(tcs.Task); + } + catch + { + CleanupTasksAndRegistrations(taskId); + throw; + } + } + + private void CleanupTasksAndRegistrations(long taskId) + { + _pendingTasks.TryRemove(taskId, out _); + if (_cancellationRegistrations.TryRemove(taskId, out var registration)) + { + registration.Dispose(); + } + } + + private async ValueTask InvokeWithDefaultCancellation(string identifier, object[] args) + { + using (var cts = new CancellationTokenSource(DefaultAsyncTimeout.Value)) + { + // We need to await here due to the using + return await InvokeAsync(identifier, cts.Token, args); + } + } + + /// + /// Begins an asynchronous function invocation. + /// + /// The identifier for the function invocation, or zero if no async callback is required. + /// The identifier for the function to invoke. + /// A JSON representation of the arguments. + protected abstract void BeginInvokeJS(long taskId, string identifier, string argsJson); + + /// + /// Completes an async JS interop call from JavaScript to .NET + /// + /// The id of the JavaScript callback to execute on completion. + /// Whether the operation succeeded or not. + /// The result of the operation or an object containing error details. + /// The name of the method assembly if the invocation was for a static method. + /// The identifier for the method within the assembly. + /// The tracking id of the dotnet object if the invocation was for an instance method. + protected internal abstract void EndInvokeDotNet( + string callId, + bool success, + object resultOrError, + string assemblyName, + string methodIdentifier, + long dotNetObjectId); + + internal void EndInvokeJS(long taskId, bool succeeded, ref Utf8JsonReader jsonReader) + { + if (!_pendingTasks.TryRemove(taskId, out var tcs)) + { + // We should simply return if we can't find an id for the invocation. + // This likely means that the method that initiated the call defined a timeout and stopped waiting. + return; + } + + CleanupTasksAndRegistrations(taskId); + + try + { + if (succeeded) + { + var resultType = TaskGenericsUtil.GetTaskCompletionSourceResultType(tcs); + + var result = JsonSerializer.Deserialize(ref jsonReader, resultType, JsonSerializerOptionsProvider.Options); + TaskGenericsUtil.SetTaskCompletionSourceResult(tcs, result); + } + else + { + var exceptionText = jsonReader.GetString() ?? string.Empty; + TaskGenericsUtil.SetTaskCompletionSourceException(tcs, new JSException(exceptionText)); + } + } + catch (Exception exception) + { + var message = $"An exception occurred executing JS interop: {exception.Message}. See InnerException for more details."; + TaskGenericsUtil.SetTaskCompletionSourceException(tcs, new JSException(message, exception)); + } + } } } diff --git a/src/JSInterop/Microsoft.JSInterop/src/JSRuntimeBase.cs b/src/JSInterop/Microsoft.JSInterop/src/JSRuntimeBase.cs deleted file mode 100644 index 2121df0523..0000000000 --- a/src/JSInterop/Microsoft.JSInterop/src/JSRuntimeBase.cs +++ /dev/null @@ -1,174 +0,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.Collections.Concurrent; -using System.Collections.Generic; -using System.Linq; -using System.Text.Json; -using System.Threading; -using System.Threading.Tasks; - -namespace Microsoft.JSInterop -{ - /// - /// Abstract base class for a JavaScript runtime. - /// - public abstract class JSRuntimeBase : IJSRuntime - { - private long _nextPendingTaskId = 1; // Start at 1 because zero signals "no response needed" - private readonly ConcurrentDictionary _pendingTasks - = new ConcurrentDictionary(); - - private readonly ConcurrentDictionary _cancellationRegistrations = - new ConcurrentDictionary(); - - internal DotNetObjectRefManager ObjectRefManager { get; } = new DotNetObjectRefManager(); - - /// - /// Gets or sets the default timeout for asynchronous JavaScript calls. - /// - protected TimeSpan? DefaultAsyncTimeout { get; set; } - - /// - /// Invokes the specified JavaScript function asynchronously. - /// - /// The JSON-serializable return type. - /// An identifier for the function to invoke. For example, the value "someScope.someFunction" will invoke the function window.someScope.someFunction. - /// JSON-serializable arguments. - /// A cancellation token to signal the cancellation of the operation. - /// An instance of obtained by JSON-deserializing the return value. - public Task InvokeAsync(string identifier, IEnumerable args, CancellationToken cancellationToken = default) - { - var taskId = Interlocked.Increment(ref _nextPendingTaskId); - var tcs = new TaskCompletionSource(TaskContinuationOptions.RunContinuationsAsynchronously); - if (cancellationToken != default) - { - _cancellationRegistrations[taskId] = cancellationToken.Register(() => - { - tcs.TrySetCanceled(cancellationToken); - CleanupTasksAndRegistrations(taskId); - }); - } - _pendingTasks[taskId] = tcs; - - try - { - if (cancellationToken.IsCancellationRequested) - { - tcs.TrySetCanceled(cancellationToken); - CleanupTasksAndRegistrations(taskId); - - return tcs.Task; - } - - var argsJson = args?.Any() == true ? - JsonSerializer.Serialize(args, JsonSerializerOptionsProvider.Options) : - null; - BeginInvokeJS(taskId, identifier, argsJson); - - return tcs.Task; - } - catch - { - CleanupTasksAndRegistrations(taskId); - throw; - } - } - - private void CleanupTasksAndRegistrations(long taskId) - { - _pendingTasks.TryRemove(taskId, out _); - if (_cancellationRegistrations.TryRemove(taskId, out var registration)) - { - registration.Dispose(); - } - } - - /// - /// Invokes the specified JavaScript function asynchronously. - /// - /// The JSON-serializable return type. - /// An identifier for the function to invoke. For example, the value "someScope.someFunction" will invoke the function window.someScope.someFunction. - /// JSON-serializable arguments. - /// An instance of obtained by JSON-deserializing the return value. - public Task InvokeAsync(string identifier, params object[] args) - { - if (!DefaultAsyncTimeout.HasValue) - { - return InvokeAsync(identifier, args, default); - } - else - { - return InvokeWithDefaultCancellation(identifier, args); - } - } - - private async Task InvokeWithDefaultCancellation(string identifier, IEnumerable args) - { - using (var cts = new CancellationTokenSource(DefaultAsyncTimeout.Value)) - { - // We need to await here due to the using - return await InvokeAsync(identifier, args, cts.Token); - } - } - - /// - /// Begins an asynchronous function invocation. - /// - /// The identifier for the function invocation, or zero if no async callback is required. - /// The identifier for the function to invoke. - /// A JSON representation of the arguments. - protected abstract void BeginInvokeJS(long taskId, string identifier, string argsJson); - - /// - /// Completes an async JS interop call from JavaScript to .NET - /// - /// The id of the JavaScript callback to execute on completion. - /// Whether the operation succeeded or not. - /// The result of the operation or an object containing error details. - /// The name of the method assembly if the invocation was for a static method. - /// The identifier for the method within the assembly. - /// The tracking id of the dotnet object if the invocation was for an instance method. - protected internal abstract void EndInvokeDotNet( - string callId, - bool success, - object resultOrError, - string assemblyName, - string methodIdentifier, - long dotNetObjectId); - - internal void EndInvokeJS(long taskId, bool succeeded, ref Utf8JsonReader jsonReader) - { - if (!_pendingTasks.TryRemove(taskId, out var tcs)) - { - // We should simply return if we can't find an id for the invocation. - // This likely means that the method that initiated the call defined a timeout and stopped waiting. - return; - } - - CleanupTasksAndRegistrations(taskId); - - try - { - if (succeeded) - { - var resultType = TaskGenericsUtil.GetTaskCompletionSourceResultType(tcs); - - var result = JsonSerializer.Deserialize(ref jsonReader, resultType, JsonSerializerOptionsProvider.Options); - TaskGenericsUtil.SetTaskCompletionSourceResult(tcs, result); - } - else - { - var exceptionText = jsonReader.GetString() ?? string.Empty; - TaskGenericsUtil.SetTaskCompletionSourceException(tcs, new JSException(exceptionText)); - } - } - catch (Exception exception) - { - var message = $"An exception occurred executing JS interop: {exception.Message}. See InnerException for more details."; - TaskGenericsUtil.SetTaskCompletionSourceException(tcs, new JSException(message, exception)); - } - } - } -} diff --git a/src/JSInterop/Microsoft.JSInterop/src/JSRuntimeExtensions.cs b/src/JSInterop/Microsoft.JSInterop/src/JSRuntimeExtensions.cs new file mode 100644 index 0000000000..ff4d3fd152 --- /dev/null +++ b/src/JSInterop/Microsoft.JSInterop/src/JSRuntimeExtensions.cs @@ -0,0 +1,140 @@ +// 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.Threading; +using System.Threading.Tasks; + +namespace Microsoft.JSInterop +{ + /// + /// Extensions for . + /// + public static class JSRuntimeExtensions + { + /// + /// Invokes the specified JavaScript function asynchronously. + /// + /// The . + /// An identifier for the function to invoke. For example, the value "someScope.someFunction" will invoke the function window.someScope.someFunction. + /// JSON-serializable arguments. + /// A that represents the asynchronous invocation operation. + public static async ValueTask InvokeVoidAsync(this IJSRuntime jsRuntime, string identifier, params object[] args) + { + if (jsRuntime is null) + { + throw new ArgumentNullException(nameof(jsRuntime)); + } + + await jsRuntime.InvokeAsync(identifier, args); + } + + /// + /// Invokes the specified JavaScript function asynchronously. + /// + /// will apply timeouts to this operation based on the value configured in . To dispatch a call with a different timeout, or no timeout, + /// consider using . + /// + /// + /// The . + /// The JSON-serializable return type. + /// An identifier for the function to invoke. For example, the value "someScope.someFunction" will invoke the function window.someScope.someFunction. + /// JSON-serializable arguments. + /// An instance of obtained by JSON-deserializing the return value. + public static ValueTask InvokeAsync(this IJSRuntime jsRuntime, string identifier, params object[] args) + { + if (jsRuntime is null) + { + throw new ArgumentNullException(nameof(jsRuntime)); + } + + return jsRuntime.InvokeAsync(identifier, args); + } + + /// + /// Invokes the specified JavaScript function asynchronously. + /// + /// The JSON-serializable return type. + /// The . + /// An identifier for the function to invoke. For example, the value "someScope.someFunction" will invoke the function window.someScope.someFunction. + /// + /// A cancellation token to signal the cancellation of the operation. Specifying this parameter will override any default cancellations such as due to timeouts + /// () from being applied. + /// + /// JSON-serializable arguments. + /// An instance of obtained by JSON-deserializing the return value. + public static ValueTask InvokeAsync(this IJSRuntime jsRuntime, string identifier, CancellationToken cancellationToken, params object[] args) + { + if (jsRuntime is null) + { + throw new ArgumentNullException(nameof(jsRuntime)); + } + + return jsRuntime.InvokeAsync(identifier, cancellationToken, args); + } + + /// + /// Invokes the specified JavaScript function asynchronously. + /// + /// The . + /// An identifier for the function to invoke. For example, the value "someScope.someFunction" will invoke the function window.someScope.someFunction. + /// + /// A cancellation token to signal the cancellation of the operation. Specifying this parameter will override any default cancellations such as due to timeouts + /// () from being applied. + /// + /// JSON-serializable arguments. + /// A that represents the asynchronous invocation operation. + public static async ValueTask InvokeVoidAsync(this IJSRuntime jsRuntime, string identifier, CancellationToken cancellationToken, params object[] args) + { + if (jsRuntime is null) + { + throw new ArgumentNullException(nameof(jsRuntime)); + } + + await jsRuntime.InvokeAsync(identifier, cancellationToken, args); + } + + /// + /// Invokes the specified JavaScript function asynchronously. + /// + /// The . + /// An identifier for the function to invoke. For example, the value "someScope.someFunction" will invoke the function window.someScope.someFunction. + /// The duration after which to cancel the async operation. Overrides default timeouts (). + /// JSON-serializable arguments. + /// A that represents the asynchronous invocation operation. + public static async ValueTask InvokeAsync(this IJSRuntime jsRuntime, string identifier, TimeSpan timeout, params object[] args) + { + if (jsRuntime is null) + { + throw new ArgumentNullException(nameof(jsRuntime)); + } + + + using var cancellationTokenSource = timeout == Timeout.InfiniteTimeSpan ? null : new CancellationTokenSource(timeout); + var cancellationToken = cancellationTokenSource?.Token ?? CancellationToken.None; + + return await jsRuntime.InvokeAsync(identifier, cancellationToken, args); + } + + /// + /// Invokes the specified JavaScript function asynchronously. + /// + /// The . + /// An identifier for the function to invoke. For example, the value "someScope.someFunction" will invoke the function window.someScope.someFunction. + /// The duration after which to cancel the async operation. Overrides default timeouts (). + /// JSON-serializable arguments. + /// A that represents the asynchronous invocation operation. + public static async ValueTask InvokeVoidAsync(this IJSRuntime jsRuntime, string identifier, TimeSpan timeout, params object[] args) + { + if (jsRuntime is null) + { + throw new ArgumentNullException(nameof(jsRuntime)); + } + + using var cancellationTokenSource = timeout == Timeout.InfiniteTimeSpan ? null : new CancellationTokenSource(timeout); + var cancellationToken = cancellationTokenSource?.Token ?? CancellationToken.None; + + await jsRuntime.InvokeAsync(identifier, cancellationToken, args); + } + } +} diff --git a/src/JSInterop/Microsoft.JSInterop/test/DotNetObjectRefTest.cs b/src/JSInterop/Microsoft.JSInterop/test/DotNetObjectReferenceTest.cs similarity index 83% rename from src/JSInterop/Microsoft.JSInterop/test/DotNetObjectRefTest.cs rename to src/JSInterop/Microsoft.JSInterop/test/DotNetObjectReferenceTest.cs index 22cb471f28..bcd5c95028 100644 --- a/src/JSInterop/Microsoft.JSInterop/test/DotNetObjectRefTest.cs +++ b/src/JSInterop/Microsoft.JSInterop/test/DotNetObjectReferenceTest.cs @@ -8,20 +8,20 @@ using static Microsoft.JSInterop.TestJSRuntime; namespace Microsoft.JSInterop { - public class DotNetObjectRefTest + public class DotNetObjectReferenceTest { [Fact] public Task CanAccessValue() => WithJSRuntime(_ => { var obj = new object(); - Assert.Same(obj, DotNetObjectRef.Create(obj).Value); + Assert.Same(obj, DotNetObjectReference.Create(obj).Value); }); [Fact] public Task NotifiesAssociatedJsRuntimeOfDisposal() => WithJSRuntime(jsRuntime => { // Arrange - var objRef = DotNetObjectRef.Create(new object()); + var objRef = DotNetObjectReference.Create(new object()); // Act Assert.Equal(1, objRef.ObjectId); diff --git a/src/JSInterop/Microsoft.JSInterop/test/DotNetDispatcherTest.cs b/src/JSInterop/Microsoft.JSInterop/test/Infrastructure/DotNetDispatcherTest.cs similarity index 91% rename from src/JSInterop/Microsoft.JSInterop/test/DotNetDispatcherTest.cs rename to src/JSInterop/Microsoft.JSInterop/test/Infrastructure/DotNetDispatcherTest.cs index 13e01c2304..d9ddac2a89 100644 --- a/src/JSInterop/Microsoft.JSInterop/test/DotNetDispatcherTest.cs +++ b/src/JSInterop/Microsoft.JSInterop/test/Infrastructure/DotNetDispatcherTest.cs @@ -9,7 +9,7 @@ using System.Threading; using System.Threading.Tasks; using Xunit; -namespace Microsoft.JSInterop +namespace Microsoft.JSInterop.Infrastructure { public class DotNetDispatcherTest { @@ -114,7 +114,7 @@ namespace Microsoft.JSInterop { // Arrange: Track a .NET object to use as an arg var arg3 = new TestDTO { IntVal = 999, StringVal = "My string" }; - var objectRef = DotNetObjectRef.Create(arg3); + var objectRef = DotNetObjectReference.Create(arg3); jsRuntime.Invoke("unimportant", objectRef); // Arrange: Remaining args @@ -142,7 +142,7 @@ namespace Microsoft.JSInterop Assert.False(resultDto2Ref.TryGetProperty(nameof(TestDTO.IntVal), out _)); Assert.True(resultDto2Ref.TryGetProperty(DotNetDispatcher.DotNetObjectRefKey.EncodedUtf8Bytes, out var property)); - var resultDto2 = Assert.IsType>(DotNetObjectRefManager.Current.FindDotNetObject(property.GetInt64())).Value; + var resultDto2 = Assert.IsType>(DotNetObjectReferenceManager.Current.FindDotNetObject(property.GetInt64())).Value; Assert.Equal("MY STRING", resultDto2.StringVal); Assert.Equal(1299, resultDto2.IntVal); }); @@ -153,7 +153,7 @@ namespace Microsoft.JSInterop // Arrange var method = nameof(SomePublicType.IncorrectDotNetObjectRefUsage); var arg3 = new TestDTO { IntVal = 999, StringVal = "My string" }; - var objectRef = DotNetObjectRef.Create(arg3); + var objectRef = DotNetObjectReference.Create(arg3); jsRuntime.Invoke("unimportant", objectRef); // Arrange: Remaining args @@ -175,7 +175,7 @@ namespace Microsoft.JSInterop { // Arrange: Track some instance var targetInstance = new SomePublicType(); - var objectRef = DotNetObjectRef.Create(targetInstance); + var objectRef = DotNetObjectReference.Create(targetInstance); jsRuntime.Invoke("unimportant", objectRef); // Act @@ -191,7 +191,7 @@ namespace Microsoft.JSInterop { // Arrange: Track some instance var targetInstance = new DerivedClass(); - var objectRef = DotNetObjectRef.Create(targetInstance); + var objectRef = DotNetObjectReference.Create(targetInstance); jsRuntime.Invoke("unimportant", objectRef); // Act @@ -207,10 +207,10 @@ namespace Microsoft.JSInterop { // Arrange var targetInstance = new SomePublicType(); - var objectRef = DotNetObjectRef.Create(targetInstance); + var objectRef = DotNetObjectReference.Create(targetInstance); // Act - DotNetDispatcher.BeginInvoke(null, null, "__Dispose", objectRef.ObjectId, null); + DotNetDispatcher.BeginInvokeDotNet(null, null, "__Dispose", objectRef.ObjectId, null); // Assert Assert.True(objectRef.Disposed); @@ -224,7 +224,7 @@ namespace Microsoft.JSInterop // Arrange: Track some instance, then dispose it var targetInstance = new SomePublicType(); - var objectRef = DotNetObjectRef.Create(targetInstance); + var objectRef = DotNetObjectReference.Create(targetInstance); jsRuntime.Invoke("unimportant", objectRef); objectRef.Dispose(); @@ -242,7 +242,7 @@ namespace Microsoft.JSInterop // Arrange: Track some instance, then dispose it var targetInstance = new SomePublicType(); - var objectRef = DotNetObjectRef.Create(targetInstance); + var objectRef = DotNetObjectReference.Create(targetInstance); jsRuntime.Invoke("unimportant", objectRef); objectRef.Dispose(); @@ -261,10 +261,10 @@ namespace Microsoft.JSInterop var argsJson = JsonSerializer.Serialize(new object[] { jsRuntime.LastInvocationAsyncHandle, true, testDTO }, JsonSerializerOptionsProvider.Options); // Act - DotNetDispatcher.EndInvoke(argsJson); + DotNetDispatcher.EndInvokeJS(argsJson); // Assert - Assert.True(task.IsCompleted && task.Status == TaskStatus.RanToCompletion); + Assert.True(task.IsCompletedSuccessfully); var result = task.Result; Assert.Equal(testDTO.StringVal, result.StringVal); Assert.Equal(testDTO.IntVal, result.IntVal); @@ -279,10 +279,10 @@ namespace Microsoft.JSInterop var argsJson = JsonSerializer.Serialize(new object[] { jsRuntime.LastInvocationAsyncHandle, false, expected }, JsonSerializerOptionsProvider.Options); // Act - DotNetDispatcher.EndInvoke(argsJson); + DotNetDispatcher.EndInvokeJS(argsJson); // Assert - var ex = await Assert.ThrowsAsync(() => task); + var ex = await Assert.ThrowsAsync(async () => await task); Assert.Equal(expected, ex.Message); }); @@ -297,7 +297,7 @@ namespace Microsoft.JSInterop // Act cts.Cancel(); - DotNetDispatcher.EndInvoke(argsJson); + DotNetDispatcher.EndInvokeJS(argsJson); // Assert Assert.True(task.IsCanceled); @@ -311,10 +311,10 @@ namespace Microsoft.JSInterop var argsJson = JsonSerializer.Serialize(new object[] { jsRuntime.LastInvocationAsyncHandle, false, null }, JsonSerializerOptionsProvider.Options); // Act - DotNetDispatcher.EndInvoke(argsJson); + DotNetDispatcher.EndInvokeJS(argsJson); // Assert - var ex = await Assert.ThrowsAsync(() => task); + var ex = await Assert.ThrowsAsync(async () => await task); Assert.Empty(ex.Message); }); @@ -325,8 +325,8 @@ namespace Microsoft.JSInterop var targetInstance = new SomePublicType(); var arg2 = new TestDTO { IntVal = 1234, StringVal = "My string" }; jsRuntime.Invoke("unimportant", - DotNetObjectRef.Create(targetInstance), - DotNetObjectRef.Create(arg2)); + DotNetObjectReference.Create(targetInstance), + DotNetObjectReference.Create(arg2)); var argsJson = "[\"myvalue\",{\"__dotNetObject\":2}]"; // Act @@ -334,7 +334,7 @@ namespace Microsoft.JSInterop // Assert Assert.Equal("[\"You passed myvalue\",{\"__dotNetObject\":3}]", resultJson); - var resultDto = ((DotNetObjectRef)jsRuntime.ObjectRefManager.FindDotNetObject(3)).Value; + var resultDto = ((DotNetObjectReference)jsRuntime.ObjectRefManager.FindDotNetObject(3)).Value; Assert.Equal(1235, resultDto.IntVal); Assert.Equal("MY STRING", resultDto.StringVal); }); @@ -362,7 +362,7 @@ namespace Microsoft.JSInterop public Task CannotInvokeWithMoreParameters() => WithJSRuntime(jsRuntime => { // Arrange - var objectRef = DotNetObjectRef.Create(new TestDTO { IntVal = 4 }); + var objectRef = DotNetObjectReference.Create(new TestDTO { IntVal = 4 }); var argsJson = JsonSerializer.Serialize(new object[] { new TestDTO { StringVal = "Another string", IntVal = 456 }, @@ -386,8 +386,8 @@ namespace Microsoft.JSInterop // Arrange: Track some instance plus another object we'll pass as a param var targetInstance = new SomePublicType(); var arg2 = new TestDTO { IntVal = 1234, StringVal = "My string" }; - var arg1Ref = DotNetObjectRef.Create(targetInstance); - var arg2Ref = DotNetObjectRef.Create(arg2); + var arg1Ref = DotNetObjectReference.Create(targetInstance); + var arg2Ref = DotNetObjectReference.Create(arg2); jsRuntime.Invoke("unimportant", arg1Ref, arg2Ref); // Arrange: all args @@ -400,7 +400,7 @@ namespace Microsoft.JSInterop // Act var callId = "123"; var resultTask = jsRuntime.NextInvocationTask; - DotNetDispatcher.BeginInvoke(callId, null, "InvokableAsyncMethod", 1, argsJson); + DotNetDispatcher.BeginInvokeDotNet(callId, null, "InvokableAsyncMethod", 1, argsJson); await resultTask; // Assert: Correct completion information @@ -413,7 +413,7 @@ namespace Microsoft.JSInterop Assert.Equal(2000, resultDto1.IntVal); // Assert: Second result value marshalled by ref - var resultDto2Ref = Assert.IsType>(result[1]); + var resultDto2Ref = Assert.IsType>(result[1]); var resultDto2 = resultDto2Ref.Value; Assert.Equal("MY STRING", resultDto2.StringVal); Assert.Equal(2468, resultDto2.IntVal); @@ -427,7 +427,7 @@ namespace Microsoft.JSInterop // Act var callId = "123"; var resultTask = jsRuntime.NextInvocationTask; - DotNetDispatcher.BeginInvoke(callId, thisAssemblyName, nameof(ThrowingClass.ThrowingMethod), default, default); + DotNetDispatcher.BeginInvokeDotNet(callId, thisAssemblyName, nameof(ThrowingClass.ThrowingMethod), default, default); await resultTask; // This won't throw, it sets properties on the jsRuntime. @@ -449,7 +449,7 @@ namespace Microsoft.JSInterop // Act var callId = "123"; var resultTask = jsRuntime.NextInvocationTask; - DotNetDispatcher.BeginInvoke(callId, thisAssemblyName, nameof(ThrowingClass.AsyncThrowingMethod), default, default); + DotNetDispatcher.BeginInvokeDotNet(callId, thisAssemblyName, nameof(ThrowingClass.AsyncThrowingMethod), default, default); await resultTask; // This won't throw, it sets properties on the jsRuntime. @@ -469,7 +469,7 @@ namespace Microsoft.JSInterop // Arrange var callId = "123"; var resultTask = jsRuntime.NextInvocationTask; - DotNetDispatcher.BeginInvoke(callId, thisAssemblyName, "InvocableStaticWithParams", default, "not json"); + DotNetDispatcher.BeginInvokeDotNet(callId, thisAssemblyName, "InvocableStaticWithParams", default, "not json"); await resultTask; // This won't throw, it sets properties on the jsRuntime. @@ -486,7 +486,7 @@ namespace Microsoft.JSInterop // Arrange var callId = "123"; var resultTask = jsRuntime.NextInvocationTask; - DotNetDispatcher.BeginInvoke(callId, null, "InvokableInstanceVoid", 1, null); + DotNetDispatcher.BeginInvokeDotNet(callId, null, "InvokableInstanceVoid", 1, null); // Assert Assert.Equal(callId, jsRuntime.LastCompletionCallId); @@ -611,7 +611,7 @@ namespace Microsoft.JSInterop DotNetDispatcher.ParseEndInvokeArguments(jsRuntime, $"[{jsRuntime.LastInvocationAsyncHandle}, true, {{\"intVal\": 7}}]"); - Assert.True(task.IsCompleted && task.Status == TaskStatus.RanToCompletion); + Assert.True(task.IsCompletedSuccessfully); Assert.Equal(7, task.Result.IntVal); } @@ -623,7 +623,7 @@ namespace Microsoft.JSInterop DotNetDispatcher.ParseEndInvokeArguments(jsRuntime, $"[{jsRuntime.LastInvocationAsyncHandle}, true, [1, 2, 3]]"); - Assert.True(task.IsCompleted && task.Status == TaskStatus.RanToCompletion); + Assert.True(task.IsCompletedSuccessfully); Assert.Equal(new[] { 1, 2, 3 }, task.Result); } @@ -635,7 +635,7 @@ namespace Microsoft.JSInterop DotNetDispatcher.ParseEndInvokeArguments(jsRuntime, $"[{jsRuntime.LastInvocationAsyncHandle}, true, null]"); - Assert.True(task.IsCompleted && task.Status == TaskStatus.RanToCompletion); + Assert.True(task.IsCompletedSuccessfully); Assert.Null(task.Result); } @@ -685,7 +685,7 @@ namespace Microsoft.JSInterop => new TestDTO { StringVal = "Test", IntVal = 123 }; [JSInvokable("InvocableStaticWithParams")] - public static object[] MyInvocableWithParams(TestDTO dtoViaJson, int[] incrementAmounts, DotNetObjectRef dtoByRef) + public static object[] MyInvocableWithParams(TestDTO dtoViaJson, int[] incrementAmounts, DotNetObjectReference dtoByRef) => new object[] { new TestDTO // Return via JSON marshalling @@ -693,7 +693,7 @@ namespace Microsoft.JSInterop StringVal = dtoViaJson.StringVal.ToUpperInvariant(), IntVal = dtoViaJson.IntVal + incrementAmounts.Sum() }, - DotNetObjectRef.Create(new TestDTO // Return by ref + DotNetObjectReference.Create(new TestDTO // Return by ref { StringVal = dtoByRef.Value.StringVal.ToUpperInvariant(), IntVal = dtoByRef.Value.IntVal + incrementAmounts.Sum() @@ -715,7 +715,7 @@ namespace Microsoft.JSInterop } [JSInvokable] - public object[] InvokableInstanceMethod(string someString, DotNetObjectRef someDTORef) + public object[] InvokableInstanceMethod(string someString, DotNetObjectReference someDTORef) { var someDTO = someDTORef.Value; // Returning an array to make the point that object references @@ -723,7 +723,7 @@ namespace Microsoft.JSInterop return new object[] { $"You passed {someString}", - DotNetObjectRef.Create(new TestDTO + DotNetObjectReference.Create(new TestDTO { IntVal = someDTO.IntVal + 1, StringVal = someDTO.StringVal.ToUpperInvariant() @@ -732,7 +732,7 @@ namespace Microsoft.JSInterop } [JSInvokable] - public async Task InvokableAsyncMethod(TestDTO dtoViaJson, DotNetObjectRef dtoByRefWrapper) + public async Task InvokableAsyncMethod(TestDTO dtoViaJson, DotNetObjectReference dtoByRefWrapper) { await Task.Delay(50); var dtoByRef = dtoByRefWrapper.Value; @@ -743,7 +743,7 @@ namespace Microsoft.JSInterop StringVal = dtoViaJson.StringVal.ToUpperInvariant(), IntVal = dtoViaJson.IntVal * 2, }, - DotNetObjectRef.Create(new TestDTO // Return by ref + DotNetObjectReference.Create(new TestDTO // Return by ref { StringVal = dtoByRef.StringVal.ToUpperInvariant(), IntVal = dtoByRef.IntVal * 2, @@ -789,7 +789,7 @@ namespace Microsoft.JSInterop } } - public class TestJSRuntime : JSInProcessRuntimeBase + public class TestJSRuntime : JSInProcessRuntime { private TaskCompletionSource _nextInvocationTcs = new TaskCompletionSource(); public Task NextInvocationTask => _nextInvocationTcs.Task; diff --git a/src/JSInterop/Microsoft.JSInterop/test/DotNetObjectReferenceJsonConverterTest.cs b/src/JSInterop/Microsoft.JSInterop/test/Infrastructure/DotNetObjectReferenceJsonConverterTest.cs similarity index 80% rename from src/JSInterop/Microsoft.JSInterop/test/DotNetObjectReferenceJsonConverterTest.cs rename to src/JSInterop/Microsoft.JSInterop/test/Infrastructure/DotNetObjectReferenceJsonConverterTest.cs index 18f3db55c1..541ad2b025 100644 --- a/src/JSInterop/Microsoft.JSInterop/test/DotNetObjectReferenceJsonConverterTest.cs +++ b/src/JSInterop/Microsoft.JSInterop/test/Infrastructure/DotNetObjectReferenceJsonConverterTest.cs @@ -6,7 +6,7 @@ using System.Threading.Tasks; using Xunit; using static Microsoft.JSInterop.TestJSRuntime; -namespace Microsoft.JSInterop.Tests +namespace Microsoft.JSInterop.Infrastructure { public class DotNetObjectReferenceJsonConverterTest { @@ -14,12 +14,12 @@ namespace Microsoft.JSInterop.Tests public Task Read_Throws_IfJsonIsMissingDotNetObjectProperty() => WithJSRuntime(_ => { // Arrange - var dotNetObjectRef = DotNetObjectRef.Create(new TestModel()); + var dotNetObjectRef = DotNetObjectReference.Create(new TestModel()); var json = "{}"; // Act & Assert - var ex = Assert.Throws(() => JsonSerializer.Deserialize>(json)); + var ex = Assert.Throws(() => JsonSerializer.Deserialize>(json)); Assert.Equal("Required property __dotNetObject not found.", ex.Message); }); @@ -27,12 +27,12 @@ namespace Microsoft.JSInterop.Tests public Task Read_Throws_IfJsonContainsUnknownContent() => WithJSRuntime(_ => { // Arrange - var dotNetObjectRef = DotNetObjectRef.Create(new TestModel()); + var dotNetObjectRef = DotNetObjectReference.Create(new TestModel()); var json = "{\"foo\":2}"; // Act & Assert - var ex = Assert.Throws(() => JsonSerializer.Deserialize>(json)); + var ex = Assert.Throws(() => JsonSerializer.Deserialize>(json)); Assert.Equal("Unexcepted JSON property foo.", ex.Message); }); @@ -41,13 +41,13 @@ namespace Microsoft.JSInterop.Tests { // Arrange var input = new TestModel(); - var dotNetObjectRef = DotNetObjectRef.Create(input); + var dotNetObjectRef = DotNetObjectReference.Create(input); var objectId = dotNetObjectRef.ObjectId; var json = $"{{\"__dotNetObject\":{objectId}"; // Act & Assert - var ex = Record.Exception(() => JsonSerializer.Deserialize>(json)); + var ex = Record.Exception(() => JsonSerializer.Deserialize>(json)); Assert.IsAssignableFrom(ex); }); @@ -56,13 +56,13 @@ namespace Microsoft.JSInterop.Tests { // Arrange var input = new TestModel(); - var dotNetObjectRef = DotNetObjectRef.Create(input); + var dotNetObjectRef = DotNetObjectReference.Create(input); var objectId = dotNetObjectRef.ObjectId; var json = $"{{\"__dotNetObject\":{objectId},\"__dotNetObject\":{objectId}}}"; // Act & Assert - var ex = Record.Exception(() => JsonSerializer.Deserialize>(json)); + var ex = Record.Exception(() => JsonSerializer.Deserialize>(json)); Assert.IsAssignableFrom(ex); }); @@ -71,13 +71,13 @@ namespace Microsoft.JSInterop.Tests { // Arrange var input = new TestModel(); - var dotNetObjectRef = DotNetObjectRef.Create(input); + var dotNetObjectRef = DotNetObjectReference.Create(input); var objectId = dotNetObjectRef.ObjectId; var json = $"{{\"__dotNetObject\":{objectId}}}"; // Act - var deserialized = JsonSerializer.Deserialize>(json); + var deserialized = JsonSerializer.Deserialize>(json); // Assert Assert.Same(input, deserialized.Value); @@ -92,13 +92,13 @@ namespace Microsoft.JSInterop.Tests // Track a few instances and verify that the deserialized value returns the correct value. var instance1 = new TestModel(); var instance2 = new TestModel(); - var ref1 = DotNetObjectRef.Create(instance1); - var ref2 = DotNetObjectRef.Create(instance2); + var ref1 = DotNetObjectReference.Create(instance1); + var ref2 = DotNetObjectReference.Create(instance2); var json = $"[{{\"__dotNetObject\":{ref2.ObjectId}}},{{\"__dotNetObject\":{ref1.ObjectId}}}]"; // Act - var deserialized = JsonSerializer.Deserialize[]>(json); + var deserialized = JsonSerializer.Deserialize[]>(json); // Assert Assert.Same(instance2, deserialized[0].Value); @@ -110,7 +110,7 @@ namespace Microsoft.JSInterop.Tests { // Arrange var input = new TestModel(); - var dotNetObjectRef = DotNetObjectRef.Create(input); + var dotNetObjectRef = DotNetObjectReference.Create(input); var objectId = dotNetObjectRef.ObjectId; var json = @@ -119,7 +119,7 @@ namespace Microsoft.JSInterop.Tests }}"; // Act - var deserialized = JsonSerializer.Deserialize>(json); + var deserialized = JsonSerializer.Deserialize>(json); // Assert Assert.Same(input, deserialized.Value); @@ -130,7 +130,7 @@ namespace Microsoft.JSInterop.Tests public Task WriteJsonTwice_KeepsObjectId() => WithJSRuntime(_ => { // Arrange - var dotNetObjectRef = DotNetObjectRef.Create(new TestModel()); + var dotNetObjectRef = DotNetObjectReference.Create(new TestModel()); // Act var json1 = JsonSerializer.Serialize(dotNetObjectRef); diff --git a/src/JSInterop/Microsoft.JSInterop/test/JSInProcessRuntimeExtensionsTest.cs b/src/JSInterop/Microsoft.JSInterop/test/JSInProcessRuntimeExtensionsTest.cs new file mode 100644 index 0000000000..3a7f0a4d79 --- /dev/null +++ b/src/JSInterop/Microsoft.JSInterop/test/JSInProcessRuntimeExtensionsTest.cs @@ -0,0 +1,27 @@ +// 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.Threading.Tasks; +using Moq; +using Xunit; + +namespace Microsoft.JSInterop +{ + public class JSInProcessRuntimeExtensionsTest + { + [Fact] + public void InvokeVoid_Works() + { + // Arrange + var method = "someMethod"; + var args = new[] { "a", "b" }; + var jsRuntime = new Mock(MockBehavior.Strict); + jsRuntime.Setup(s => s.Invoke(method, args)).Returns(new ValueTask(new object())); + + // Act + jsRuntime.Object.InvokeVoid(method, args); + + jsRuntime.Verify(); + } + } +} diff --git a/src/JSInterop/Microsoft.JSInterop/test/JSInProcessRuntimeBaseTest.cs b/src/JSInterop/Microsoft.JSInterop/test/JSInProcessRuntimeTest.cs similarity index 88% rename from src/JSInterop/Microsoft.JSInterop/test/JSInProcessRuntimeBaseTest.cs rename to src/JSInterop/Microsoft.JSInterop/test/JSInProcessRuntimeTest.cs index d71969d450..4054101258 100644 --- a/src/JSInterop/Microsoft.JSInterop/test/JSInProcessRuntimeBaseTest.cs +++ b/src/JSInterop/Microsoft.JSInterop/test/JSInProcessRuntimeTest.cs @@ -6,7 +6,7 @@ using System.Collections.Generic; using System.Linq; using Xunit; -namespace Microsoft.JSInterop.Tests +namespace Microsoft.JSInterop { public class JSInProcessRuntimeBaseTest { @@ -43,12 +43,12 @@ namespace Microsoft.JSInterop.Tests // Act // Showing we can pass the DotNetObject either as top-level args or nested - var syncResult = runtime.Invoke>("test identifier", - DotNetObjectRef.Create(obj1), + var syncResult = runtime.Invoke>("test identifier", + DotNetObjectReference.Create(obj1), new Dictionary { - { "obj2", DotNetObjectRef.Create(obj2) }, - { "obj3", DotNetObjectRef.Create(obj3) }, + { "obj2", DotNetObjectReference.Create(obj2) }, + { "obj3", DotNetObjectReference.Create(obj3) }, }); // Assert: Handles null result string @@ -78,11 +78,11 @@ namespace Microsoft.JSInterop.Tests var obj2 = new object(); // Act - var syncResult = runtime.Invoke[]>( + var syncResult = runtime.Invoke[]>( "test identifier", - DotNetObjectRef.Create(obj1), + DotNetObjectReference.Create(obj1), "some other arg", - DotNetObjectRef.Create(obj2)); + DotNetObjectReference.Create(obj2)); var call = runtime.InvokeCalls.Single(); // Assert @@ -95,7 +95,7 @@ namespace Microsoft.JSInterop.Tests public string StringValue { get; set; } } - class TestJSInProcessRuntime : JSInProcessRuntimeBase + class TestJSInProcessRuntime : JSInProcessRuntime { public List InvokeCalls { get; set; } = new List(); diff --git a/src/JSInterop/Microsoft.JSInterop/test/JSRuntimeBaseTest.cs b/src/JSInterop/Microsoft.JSInterop/test/JSRuntimeBaseTest.cs deleted file mode 100644 index c3bf4f9eef..0000000000 --- a/src/JSInterop/Microsoft.JSInterop/test/JSRuntimeBaseTest.cs +++ /dev/null @@ -1,386 +0,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.Collections.Generic; -using System.Linq; -using System.Text; -using System.Text.Json; -using System.Threading; -using System.Threading.Tasks; -using Xunit; - -namespace Microsoft.JSInterop -{ - public class JSRuntimeBaseTest - { - [Fact] - public void DispatchesAsyncCallsWithDistinctAsyncHandles() - { - // Arrange - var runtime = new TestJSRuntime(); - - // Act - runtime.InvokeAsync("test identifier 1", "arg1", 123, true); - runtime.InvokeAsync("test identifier 2", "some other arg"); - - // Assert - Assert.Collection(runtime.BeginInvokeCalls, - call => - { - Assert.Equal("test identifier 1", call.Identifier); - Assert.Equal("[\"arg1\",123,true]", call.ArgsJson); - }, - call => - { - Assert.Equal("test identifier 2", call.Identifier); - Assert.Equal("[\"some other arg\"]", call.ArgsJson); - Assert.NotEqual(runtime.BeginInvokeCalls[0].AsyncHandle, call.AsyncHandle); - }); - } - - [Fact] - public async Task InvokeAsync_CancelsAsyncTask_AfterDefaultTimeout() - { - // Arrange - var runtime = new TestJSRuntime(); - runtime.DefaultTimeout = TimeSpan.FromSeconds(1); - - // Act - var task = runtime.InvokeAsync("test identifier 1", "arg1", 123, true); - - // Assert - await Assert.ThrowsAsync(async () => await task); - } - - [Fact] - public void InvokeAsync_CompletesSuccessfullyBeforeTimeout() - { - // Arrange - var runtime = new TestJSRuntime(); - runtime.DefaultTimeout = TimeSpan.FromSeconds(10); - var reader = new Utf8JsonReader(Encoding.UTF8.GetBytes("null")); - - // Act - var task = runtime.InvokeAsync("test identifier 1", "arg1", 123, true); - - runtime.EndInvokeJS(2, succeeded: true, ref reader); - - Assert.True(task.IsCompleted && task.Status == TaskStatus.RanToCompletion); - } - - [Fact] - public async Task InvokeAsync_CancelsAsyncTasksWhenCancellationTokenFires() - { - // Arrange - using var cts = new CancellationTokenSource(); - var runtime = new TestJSRuntime(); - - // Act - var task = runtime.InvokeAsync("test identifier 1", new object[] { "arg1", 123, true }, cts.Token); - - cts.Cancel(); - - // Assert - await Assert.ThrowsAsync(async () => await task); - } - - [Fact] - public async Task InvokeAsync_DoesNotStartWorkWhenCancellationHasBeenRequested() - { - // Arrange - using var cts = new CancellationTokenSource(); - cts.Cancel(); - var runtime = new TestJSRuntime(); - - // Act - var task = runtime.InvokeAsync("test identifier 1", new object[] { "arg1", 123, true }, cts.Token); - - cts.Cancel(); - - // Assert - await Assert.ThrowsAsync(async () => await task); - Assert.Empty(runtime.BeginInvokeCalls); - } - - [Fact] - public void CanCompleteAsyncCallsAsSuccess() - { - // Arrange - var runtime = new TestJSRuntime(); - - // Act/Assert: Tasks not initially completed - var unrelatedTask = runtime.InvokeAsync("unrelated call", Array.Empty()); - var task = runtime.InvokeAsync("test identifier", Array.Empty()); - Assert.False(unrelatedTask.IsCompleted); - Assert.False(task.IsCompleted); - var bytes = Encoding.UTF8.GetBytes("\"my result\""); - var reader = new Utf8JsonReader(bytes); - - // Act/Assert: Task can be completed - runtime.EndInvokeJS( - runtime.BeginInvokeCalls[1].AsyncHandle, - /* succeeded: */ true, - ref reader); - Assert.False(unrelatedTask.IsCompleted); - Assert.True(task.IsCompleted); - Assert.Equal("my result", task.Result); - } - - [Fact] - public void CanCompleteAsyncCallsWithComplexType() - { - // Arrange - var runtime = new TestJSRuntime(); - - var task = runtime.InvokeAsync("test identifier", Array.Empty()); - var bytes = Encoding.UTF8.GetBytes("{\"id\":10, \"name\": \"Test\"}"); - var reader = new Utf8JsonReader(bytes); - - // Act/Assert: Task can be completed - runtime.EndInvokeJS( - runtime.BeginInvokeCalls[0].AsyncHandle, - /* succeeded: */ true, - ref reader); - Assert.True(task.IsCompleted); - var poco = task.Result; - Assert.Equal(10, poco.Id); - Assert.Equal("Test", poco.Name); - } - - [Fact] - public void CanCompleteAsyncCallsWithComplexTypeUsingPropertyCasing() - { - // Arrange - var runtime = new TestJSRuntime(); - - var task = runtime.InvokeAsync("test identifier", Array.Empty()); - var bytes = Encoding.UTF8.GetBytes("{\"Id\":10, \"Name\": \"Test\"}"); - var reader = new Utf8JsonReader(bytes); - reader.Read(); - - // Act/Assert: Task can be completed - runtime.EndInvokeJS( - runtime.BeginInvokeCalls[0].AsyncHandle, - /* succeeded: */ true, - ref reader); - Assert.True(task.IsCompleted); - var poco = task.Result; - Assert.Equal(10, poco.Id); - Assert.Equal("Test", poco.Name); - } - - [Fact] - public void CanCompleteAsyncCallsAsFailure() - { - // Arrange - var runtime = new TestJSRuntime(); - - // Act/Assert: Tasks not initially completed - var unrelatedTask = runtime.InvokeAsync("unrelated call", Array.Empty()); - var task = runtime.InvokeAsync("test identifier", Array.Empty()); - Assert.False(unrelatedTask.IsCompleted); - Assert.False(task.IsCompleted); - var bytes = Encoding.UTF8.GetBytes("\"This is a test exception\""); - var reader = new Utf8JsonReader(bytes); - reader.Read(); - - // Act/Assert: Task can be failed - runtime.EndInvokeJS( - runtime.BeginInvokeCalls[1].AsyncHandle, - /* succeeded: */ false, - ref reader); - Assert.False(unrelatedTask.IsCompleted); - Assert.True(task.IsCompleted); - - Assert.IsType(task.Exception); - Assert.IsType(task.Exception.InnerException); - Assert.Equal("This is a test exception", ((JSException)task.Exception.InnerException).Message); - } - - [Fact] - public Task CanCompleteAsyncCallsWithErrorsDuringDeserialization() - { - // Arrange - var runtime = new TestJSRuntime(); - - // Act/Assert: Tasks not initially completed - var unrelatedTask = runtime.InvokeAsync("unrelated call", Array.Empty()); - var task = runtime.InvokeAsync("test identifier", Array.Empty()); - Assert.False(unrelatedTask.IsCompleted); - Assert.False(task.IsCompleted); - var bytes = Encoding.UTF8.GetBytes("Not a string"); - var reader = new Utf8JsonReader(bytes); - - // Act/Assert: Task can be failed - runtime.EndInvokeJS( - runtime.BeginInvokeCalls[1].AsyncHandle, - /* succeeded: */ true, - ref reader); - Assert.False(unrelatedTask.IsCompleted); - - return AssertTask(); - - async Task AssertTask() - { - var jsException = await Assert.ThrowsAsync(() => task); - Assert.IsAssignableFrom(jsException.InnerException); - } - } - - [Fact] - public Task CompletingSameAsyncCallMoreThanOnce_IgnoresSecondResultAsync() - { - // Arrange - var runtime = new TestJSRuntime(); - - // Act/Assert - var task = runtime.InvokeAsync("test identifier", Array.Empty()); - var asyncHandle = runtime.BeginInvokeCalls[0].AsyncHandle; - var firstReader = new Utf8JsonReader(Encoding.UTF8.GetBytes("\"Some data\"")); - var secondReader = new Utf8JsonReader(Encoding.UTF8.GetBytes("\"Exception\"")); - - runtime.EndInvokeJS(asyncHandle, true, ref firstReader); - runtime.EndInvokeJS(asyncHandle, false, ref secondReader); - - return AssertTask(); - - async Task AssertTask() - { - var result = await task; - Assert.Equal("Some data", result); - } - } - - [Fact] - public void SerializesDotNetObjectWrappersInKnownFormat() - { - // Arrange - var runtime = new TestJSRuntime(); - JSRuntime.SetCurrentJSRuntime(runtime); - var obj1 = new object(); - var obj2 = new object(); - var obj3 = new object(); - - // Act - // Showing we can pass the DotNetObject either as top-level args or nested - var obj1Ref = DotNetObjectRef.Create(obj1); - var obj1DifferentRef = DotNetObjectRef.Create(obj1); - runtime.InvokeAsync("test identifier", - obj1Ref, - new Dictionary - { - { "obj2", DotNetObjectRef.Create(obj2) }, - { "obj3", DotNetObjectRef.Create(obj3) }, - { "obj1SameRef", obj1Ref }, - { "obj1DifferentRef", obj1DifferentRef }, - }); - - // Assert: Serialized as expected - var call = runtime.BeginInvokeCalls.Single(); - Assert.Equal("test identifier", call.Identifier); - Assert.Equal("[{\"__dotNetObject\":1},{\"obj2\":{\"__dotNetObject\":3},\"obj3\":{\"__dotNetObject\":4},\"obj1SameRef\":{\"__dotNetObject\":1},\"obj1DifferentRef\":{\"__dotNetObject\":2}}]", call.ArgsJson); - - // Assert: Objects were tracked - Assert.Same(obj1, runtime.ObjectRefManager.FindDotNetObject(1).Value); - Assert.Same(obj1, runtime.ObjectRefManager.FindDotNetObject(2).Value); - Assert.Same(obj2, runtime.ObjectRefManager.FindDotNetObject(3).Value); - Assert.Same(obj3, runtime.ObjectRefManager.FindDotNetObject(4).Value); - } - - [Fact] - public void CanSanitizeDotNetInteropExceptions() - { - // Arrange - var expectedMessage = "An error ocurred while invoking '[Assembly]::Method'. Swapping to 'Development' environment will " + - "display more detailed information about the error that occurred."; - - string GetMessage(string assembly, string method) => $"An error ocurred while invoking '[{assembly}]::{method}'. Swapping to 'Development' environment will " + - "display more detailed information about the error that occurred."; - - var runtime = new TestJSRuntime() - { - OnDotNetException = (e, a, m) => new JSError { Message = GetMessage(a, m) } - }; - - var exception = new Exception("Some really sensitive data in here"); - - // Act - runtime.EndInvokeDotNet("0", false, exception, "Assembly", "Method", 0); - - // Assert - var call = runtime.EndInvokeDotNetCalls.Single(); - Assert.Equal("0", call.CallId); - Assert.False(call.Success); - var jsError = Assert.IsType(call.ResultOrError); - Assert.Equal(expectedMessage, jsError.Message); - } - - private class JSError - { - public string Message { get; set; } - } - - private class TestPoco - { - public int Id { get; set; } - - public string Name { get; set; } - } - - class TestJSRuntime : JSRuntimeBase - { - public List BeginInvokeCalls = new List(); - public List EndInvokeDotNetCalls = new List(); - - public TimeSpan? DefaultTimeout - { - set - { - base.DefaultAsyncTimeout = value; - } - } - - public class BeginInvokeAsyncArgs - { - public long AsyncHandle { get; set; } - public string Identifier { get; set; } - public string ArgsJson { get; set; } - } - - public class EndInvokeDotNetArgs - { - public string CallId { get; set; } - public bool Success { get; set; } - public object ResultOrError { get; set; } - } - - public Func OnDotNetException { get; set; } - - protected internal override void EndInvokeDotNet(string callId, bool success, object resultOrError, string assemblyName, string methodIdentifier, long dotNetObjectId) - { - if (OnDotNetException != null && !success) - { - resultOrError = OnDotNetException(resultOrError as Exception, assemblyName, methodIdentifier); - } - - EndInvokeDotNetCalls.Add(new EndInvokeDotNetArgs - { - CallId = callId, - Success = success, - ResultOrError = resultOrError - }); - } - - protected override void BeginInvokeJS(long asyncHandle, string identifier, string argsJson) - { - BeginInvokeCalls.Add(new BeginInvokeAsyncArgs - { - AsyncHandle = asyncHandle, - Identifier = identifier, - ArgsJson = argsJson, - }); - } - } - } -} diff --git a/src/JSInterop/Microsoft.JSInterop/test/JSRuntimeExtensionsTest.cs b/src/JSInterop/Microsoft.JSInterop/test/JSRuntimeExtensionsTest.cs new file mode 100644 index 0000000000..a5f69fbef2 --- /dev/null +++ b/src/JSInterop/Microsoft.JSInterop/test/JSRuntimeExtensionsTest.cs @@ -0,0 +1,181 @@ +// 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.Threading; +using System.Threading.Tasks; +using Moq; +using Xunit; + +namespace Microsoft.JSInterop +{ + public class JSRuntimeExtensionsTest + { + [Fact] + public async Task InvokeAsync_WithParamsArgs() + { + // Arrange + var method = "someMethod"; + var expected = new[] { "a", "b" }; + var jsRuntime = new Mock(MockBehavior.Strict); + jsRuntime.Setup(s => s.InvokeAsync(method, It.IsAny())) + .Callback((method, args) => + { + Assert.Equal(expected, args); + }) + .Returns(new ValueTask("Hello")) + .Verifiable(); + + // Act + var result = await jsRuntime.Object.InvokeAsync(method, "a", "b"); + + // Assert + Assert.Equal("Hello", result); + jsRuntime.Verify(); + } + + [Fact] + public async Task InvokeAsync_WithParamsArgsAndCancellationToken() + { + // Arrange + var method = "someMethod"; + var expected = new[] { "a", "b" }; + var cancellationToken = new CancellationToken(); + var jsRuntime = new Mock(MockBehavior.Strict); + jsRuntime.Setup(s => s.InvokeAsync(method, cancellationToken, It.IsAny())) + .Callback((method, cts, args) => + { + Assert.Equal(expected, args); + }) + .Returns(new ValueTask("Hello")) + .Verifiable(); + + // Act + var result = await jsRuntime.Object.InvokeAsync(method, cancellationToken, "a", "b"); + + // Assert + Assert.Equal("Hello", result); + jsRuntime.Verify(); + } + + [Fact] + public async Task InvokeVoidAsync_WithoutCancellationToken() + { + // Arrange + var method = "someMethod"; + var args = new[] { "a", "b" }; + var jsRuntime = new Mock(MockBehavior.Strict); + jsRuntime.Setup(s => s.InvokeAsync(method, args)).Returns(new ValueTask(new object())); + + // Act + await jsRuntime.Object.InvokeVoidAsync(method, args); + + jsRuntime.Verify(); + } + + [Fact] + public async Task InvokeVoidAsync_WithCancellationToken() + { + // Arrange + var method = "someMethod"; + var args = new[] { "a", "b" }; + var jsRuntime = new Mock(MockBehavior.Strict); + jsRuntime.Setup(s => s.InvokeAsync(method, It.IsAny(), args)).Returns(new ValueTask(new object())); + + // Act + await jsRuntime.Object.InvokeVoidAsync(method, new CancellationToken(), args); + + jsRuntime.Verify(); + } + + [Fact] + public async Task InvokeAsync_WithTimeout() + { + // Arrange + var expected = "Hello"; + var method = "someMethod"; + var args = new[] { "a", "b" }; + var jsRuntime = new Mock(MockBehavior.Strict); + jsRuntime.Setup(s => s.InvokeAsync(method, It.IsAny(), args)) + .Callback((method, cts, args) => + { + // There isn't a very good way to test when the cts will cancel. We'll just verify that + // it'll get cancelled eventually. + Assert.True(cts.CanBeCanceled); + }) + .Returns(new ValueTask(expected)); + + // Act + var result = await jsRuntime.Object.InvokeAsync(method, TimeSpan.FromMinutes(5), args); + + Assert.Equal(expected, result); + jsRuntime.Verify(); + } + + [Fact] + public async Task InvokeAsync_WithInfiniteTimeout() + { + // Arrange + var expected = "Hello"; + var method = "someMethod"; + var args = new[] { "a", "b" }; + var jsRuntime = new Mock(MockBehavior.Strict); + jsRuntime.Setup(s => s.InvokeAsync(method, It.IsAny(), args)) + .Callback((method, cts, args) => + { + Assert.False(cts.CanBeCanceled); + Assert.True(cts == CancellationToken.None); + }) + .Returns(new ValueTask(expected)); + + // Act + var result = await jsRuntime.Object.InvokeAsync(method, Timeout.InfiniteTimeSpan, args); + + Assert.Equal(expected, result); + jsRuntime.Verify(); + } + + [Fact] + public async Task InvokeVoidAsync_WithTimeout() + { + // Arrange + var method = "someMethod"; + var args = new[] { "a", "b" }; + var jsRuntime = new Mock(MockBehavior.Strict); + jsRuntime.Setup(s => s.InvokeAsync(method, It.IsAny(), args)) + .Callback((method, cts, args) => + { + // There isn't a very good way to test when the cts will cancel. We'll just verify that + // it'll get cancelled eventually. + Assert.True(cts.CanBeCanceled); + }) + .Returns(new ValueTask(new object())); + + // Act + await jsRuntime.Object.InvokeVoidAsync(method, TimeSpan.FromMinutes(5), args); + + jsRuntime.Verify(); + } + + [Fact] + public async Task InvokeVoidAsync_WithInfiniteTimeout() + { + // Arrange + var method = "someMethod"; + var args = new[] { "a", "b" }; + var jsRuntime = new Mock(MockBehavior.Strict); + jsRuntime.Setup(s => s.InvokeAsync(method, It.IsAny(), args)) + .Callback((method, cts, args) => + { + Assert.False(cts.CanBeCanceled); + Assert.True(cts == CancellationToken.None); + }) + .Returns(new ValueTask(new object())); + + // Act + await jsRuntime.Object.InvokeVoidAsync(method, Timeout.InfiniteTimeSpan, args); + + jsRuntime.Verify(); + } + } +} diff --git a/src/JSInterop/Microsoft.JSInterop/test/JSRuntimeTest.cs b/src/JSInterop/Microsoft.JSInterop/test/JSRuntimeTest.cs index f2fab5d741..4e65ddeb0f 100644 --- a/src/JSInterop/Microsoft.JSInterop/test/JSRuntimeTest.cs +++ b/src/JSInterop/Microsoft.JSInterop/test/JSRuntimeTest.cs @@ -4,20 +4,23 @@ using System; using System.Collections.Generic; using System.Linq; +using System.Text; +using System.Text.Json; using System.Threading; using System.Threading.Tasks; using Xunit; -namespace Microsoft.JSInterop.Tests +namespace Microsoft.JSInterop { public class JSRuntimeTest { + #region this will be removed eventually [Fact] public async Task CanHaveDistinctJSRuntimeInstancesInEachAsyncContext() { var tasks = Enumerable.Range(0, 20).Select(async _ => { - var jsRuntime = new FakeJSRuntime(); + var jsRuntime = new TestJSRuntime(); JSRuntime.SetCurrentJSRuntime(jsRuntime); await Task.Delay(50).ConfigureAwait(false); Assert.Same(jsRuntime, JSRuntime.Current); @@ -26,14 +29,377 @@ namespace Microsoft.JSInterop.Tests await Task.WhenAll(tasks); Assert.Null(JSRuntime.Current); } + #endregion - private class FakeJSRuntime : IJSRuntime + [Fact] + public void DispatchesAsyncCallsWithDistinctAsyncHandles() { - public Task InvokeAsync(string identifier, params object[] args) - => throw new NotImplementedException(); + // Arrange + var runtime = new TestJSRuntime(); - public Task InvokeAsync(string identifier, IEnumerable args, CancellationToken cancellationToken = default) => - throw new NotImplementedException(); + // Act + runtime.InvokeAsync("test identifier 1", "arg1", 123, true); + runtime.InvokeAsync("test identifier 2", "some other arg"); + + // Assert + Assert.Collection(runtime.BeginInvokeCalls, + call => + { + Assert.Equal("test identifier 1", call.Identifier); + Assert.Equal("[\"arg1\",123,true]", call.ArgsJson); + }, + call => + { + Assert.Equal("test identifier 2", call.Identifier); + Assert.Equal("[\"some other arg\"]", call.ArgsJson); + Assert.NotEqual(runtime.BeginInvokeCalls[0].AsyncHandle, call.AsyncHandle); + }); + } + + [Fact] + public async Task InvokeAsync_CancelsAsyncTask_AfterDefaultTimeout() + { + // Arrange + var runtime = new TestJSRuntime(); + runtime.DefaultTimeout = TimeSpan.FromSeconds(1); + + // Act + var task = runtime.InvokeAsync("test identifier 1", "arg1", 123, true); + + // Assert + await Assert.ThrowsAsync(async () => await task); + } + + [Fact] + public void InvokeAsync_CompletesSuccessfullyBeforeTimeout() + { + // Arrange + var runtime = new TestJSRuntime(); + runtime.DefaultTimeout = TimeSpan.FromSeconds(10); + var reader = new Utf8JsonReader(Encoding.UTF8.GetBytes("null")); + + // Act + var task = runtime.InvokeAsync("test identifier 1", "arg1", 123, true); + + runtime.EndInvokeJS(2, succeeded: true, ref reader); + + Assert.True(task.IsCompletedSuccessfully); + } + + [Fact] + public async Task InvokeAsync_CancelsAsyncTasksWhenCancellationTokenFires() + { + // Arrange + using var cts = new CancellationTokenSource(); + var runtime = new TestJSRuntime(); + + // Act + var task = runtime.InvokeAsync("test identifier 1", cts.Token, new object[] { "arg1", 123, true }); + + cts.Cancel(); + + // Assert + await Assert.ThrowsAsync(async () => await task); + } + + [Fact] + public async Task InvokeAsync_DoesNotStartWorkWhenCancellationHasBeenRequested() + { + // Arrange + using var cts = new CancellationTokenSource(); + cts.Cancel(); + var runtime = new TestJSRuntime(); + + // Act + var task = runtime.InvokeAsync("test identifier 1", cts.Token, new object[] { "arg1", 123, true }); + + cts.Cancel(); + + // Assert + await Assert.ThrowsAsync(async () => await task); + Assert.Empty(runtime.BeginInvokeCalls); + } + + [Fact] + public void CanCompleteAsyncCallsAsSuccess() + { + // Arrange + var runtime = new TestJSRuntime(); + + // Act/Assert: Tasks not initially completed + var unrelatedTask = runtime.InvokeAsync("unrelated call", Array.Empty()); + var task = runtime.InvokeAsync("test identifier", Array.Empty()); + Assert.False(unrelatedTask.IsCompleted); + Assert.False(task.IsCompleted); + var bytes = Encoding.UTF8.GetBytes("\"my result\""); + var reader = new Utf8JsonReader(bytes); + + // Act/Assert: Task can be completed + runtime.EndInvokeJS( + runtime.BeginInvokeCalls[1].AsyncHandle, + /* succeeded: */ true, + ref reader); + Assert.False(unrelatedTask.IsCompleted); + Assert.True(task.IsCompleted); + Assert.Equal("my result", task.Result); + } + + [Fact] + public void CanCompleteAsyncCallsWithComplexType() + { + // Arrange + var runtime = new TestJSRuntime(); + + var task = runtime.InvokeAsync("test identifier", Array.Empty()); + var bytes = Encoding.UTF8.GetBytes("{\"id\":10, \"name\": \"Test\"}"); + var reader = new Utf8JsonReader(bytes); + + // Act/Assert: Task can be completed + runtime.EndInvokeJS( + runtime.BeginInvokeCalls[0].AsyncHandle, + /* succeeded: */ true, + ref reader); + Assert.True(task.IsCompleted); + var poco = task.Result; + Assert.Equal(10, poco.Id); + Assert.Equal("Test", poco.Name); + } + + [Fact] + public void CanCompleteAsyncCallsWithComplexTypeUsingPropertyCasing() + { + // Arrange + var runtime = new TestJSRuntime(); + + var task = runtime.InvokeAsync("test identifier", Array.Empty()); + var bytes = Encoding.UTF8.GetBytes("{\"Id\":10, \"Name\": \"Test\"}"); + var reader = new Utf8JsonReader(bytes); + reader.Read(); + + // Act/Assert: Task can be completed + runtime.EndInvokeJS( + runtime.BeginInvokeCalls[0].AsyncHandle, + /* succeeded: */ true, + ref reader); + Assert.True(task.IsCompleted); + var poco = task.Result; + Assert.Equal(10, poco.Id); + Assert.Equal("Test", poco.Name); + } + + [Fact] + public void CanCompleteAsyncCallsAsFailure() + { + // Arrange + var runtime = new TestJSRuntime(); + + // Act/Assert: Tasks not initially completed + var unrelatedTask = runtime.InvokeAsync("unrelated call", Array.Empty()); + var task = runtime.InvokeAsync("test identifier", Array.Empty()); + Assert.False(unrelatedTask.IsCompleted); + Assert.False(task.IsCompleted); + var bytes = Encoding.UTF8.GetBytes("\"This is a test exception\""); + var reader = new Utf8JsonReader(bytes); + reader.Read(); + + // Act/Assert: Task can be failed + runtime.EndInvokeJS( + runtime.BeginInvokeCalls[1].AsyncHandle, + /* succeeded: */ false, + ref reader); + Assert.False(unrelatedTask.IsCompleted); + Assert.True(task.IsCompleted); + + var exception = Assert.IsType(task.AsTask().Exception); + var jsException = Assert.IsType(exception.InnerException); + Assert.Equal("This is a test exception", jsException.Message); + } + + [Fact] + public Task CanCompleteAsyncCallsWithErrorsDuringDeserialization() + { + // Arrange + var runtime = new TestJSRuntime(); + + // Act/Assert: Tasks not initially completed + var unrelatedTask = runtime.InvokeAsync("unrelated call", Array.Empty()); + var task = runtime.InvokeAsync("test identifier", Array.Empty()); + Assert.False(unrelatedTask.IsCompleted); + Assert.False(task.IsCompleted); + var bytes = Encoding.UTF8.GetBytes("Not a string"); + var reader = new Utf8JsonReader(bytes); + + // Act/Assert: Task can be failed + runtime.EndInvokeJS( + runtime.BeginInvokeCalls[1].AsyncHandle, + /* succeeded: */ true, + ref reader); + Assert.False(unrelatedTask.IsCompleted); + + return AssertTask(); + + async Task AssertTask() + { + var jsException = await Assert.ThrowsAsync(async () => await task); + Assert.IsAssignableFrom(jsException.InnerException); + } + } + + [Fact] + public Task CompletingSameAsyncCallMoreThanOnce_IgnoresSecondResultAsync() + { + // Arrange + var runtime = new TestJSRuntime(); + + // Act/Assert + var task = runtime.InvokeAsync("test identifier", Array.Empty()); + var asyncHandle = runtime.BeginInvokeCalls[0].AsyncHandle; + var firstReader = new Utf8JsonReader(Encoding.UTF8.GetBytes("\"Some data\"")); + var secondReader = new Utf8JsonReader(Encoding.UTF8.GetBytes("\"Exception\"")); + + runtime.EndInvokeJS(asyncHandle, true, ref firstReader); + runtime.EndInvokeJS(asyncHandle, false, ref secondReader); + + return AssertTask(); + + async Task AssertTask() + { + var result = await task; + Assert.Equal("Some data", result); + } + } + + [Fact] + public void SerializesDotNetObjectWrappersInKnownFormat() + { + // Arrange + var runtime = new TestJSRuntime(); + JSRuntime.SetCurrentJSRuntime(runtime); + var obj1 = new object(); + var obj2 = new object(); + var obj3 = new object(); + + // Act + // Showing we can pass the DotNetObject either as top-level args or nested + var obj1Ref = DotNetObjectReference.Create(obj1); + var obj1DifferentRef = DotNetObjectReference.Create(obj1); + runtime.InvokeAsync("test identifier", + obj1Ref, + new Dictionary + { + { "obj2", DotNetObjectReference.Create(obj2) }, + { "obj3", DotNetObjectReference.Create(obj3) }, + { "obj1SameRef", obj1Ref }, + { "obj1DifferentRef", obj1DifferentRef }, + }); + + // Assert: Serialized as expected + var call = runtime.BeginInvokeCalls.Single(); + Assert.Equal("test identifier", call.Identifier); + Assert.Equal("[{\"__dotNetObject\":1},{\"obj2\":{\"__dotNetObject\":3},\"obj3\":{\"__dotNetObject\":4},\"obj1SameRef\":{\"__dotNetObject\":1},\"obj1DifferentRef\":{\"__dotNetObject\":2}}]", call.ArgsJson); + + // Assert: Objects were tracked + Assert.Same(obj1Ref, runtime.ObjectRefManager.FindDotNetObject(1)); + Assert.Same(obj1, obj1Ref.Value); + Assert.NotSame(obj1Ref, runtime.ObjectRefManager.FindDotNetObject(2)); + Assert.Same(obj1, runtime.ObjectRefManager.FindDotNetObject(2).Value); + Assert.Same(obj2, runtime.ObjectRefManager.FindDotNetObject(3).Value); + Assert.Same(obj3, runtime.ObjectRefManager.FindDotNetObject(4).Value); + } + + [Fact] + public void CanSanitizeDotNetInteropExceptions() + { + // Arrange + var expectedMessage = "An error ocurred while invoking '[Assembly]::Method'. Swapping to 'Development' environment will " + + "display more detailed information about the error that occurred."; + + string GetMessage(string assembly, string method) => $"An error ocurred while invoking '[{assembly}]::{method}'. Swapping to 'Development' environment will " + + "display more detailed information about the error that occurred."; + + var runtime = new TestJSRuntime() + { + OnDotNetException = (e, a, m) => new JSError { Message = GetMessage(a, m) } + }; + + var exception = new Exception("Some really sensitive data in here"); + + // Act + runtime.EndInvokeDotNet("0", false, exception, "Assembly", "Method", 0); + + // Assert + var call = runtime.EndInvokeDotNetCalls.Single(); + Assert.Equal("0", call.CallId); + Assert.False(call.Success); + var jsError = Assert.IsType(call.ResultOrError); + Assert.Equal(expectedMessage, jsError.Message); + } + + private class JSError + { + public string Message { get; set; } + } + + private class TestPoco + { + public int Id { get; set; } + + public string Name { get; set; } + } + + class TestJSRuntime : JSRuntime + { + public List BeginInvokeCalls = new List(); + public List EndInvokeDotNetCalls = new List(); + + public TimeSpan? DefaultTimeout + { + set + { + base.DefaultAsyncTimeout = value; + } + } + + public class BeginInvokeAsyncArgs + { + public long AsyncHandle { get; set; } + public string Identifier { get; set; } + public string ArgsJson { get; set; } + } + + public class EndInvokeDotNetArgs + { + public string CallId { get; set; } + public bool Success { get; set; } + public object ResultOrError { get; set; } + } + + public Func OnDotNetException { get; set; } + + protected internal override void EndInvokeDotNet(string callId, bool success, object resultOrError, string assemblyName, string methodIdentifier, long dotNetObjectId) + { + if (OnDotNetException != null && !success) + { + resultOrError = OnDotNetException(resultOrError as Exception, assemblyName, methodIdentifier); + } + + EndInvokeDotNetCalls.Add(new EndInvokeDotNetArgs + { + CallId = callId, + Success = success, + ResultOrError = resultOrError + }); + } + + protected override void BeginInvokeJS(long asyncHandle, string identifier, string argsJson) + { + BeginInvokeCalls.Add(new BeginInvokeAsyncArgs + { + AsyncHandle = asyncHandle, + Identifier = identifier, + ArgsJson = argsJson, + }); + } } } } diff --git a/src/JSInterop/Microsoft.JSInterop/test/TestJSRuntime.cs b/src/JSInterop/Microsoft.JSInterop/test/TestJSRuntime.cs index c4e6b05c5b..48782fc4df 100644 --- a/src/JSInterop/Microsoft.JSInterop/test/TestJSRuntime.cs +++ b/src/JSInterop/Microsoft.JSInterop/test/TestJSRuntime.cs @@ -6,7 +6,7 @@ using System.Threading.Tasks; namespace Microsoft.JSInterop { - internal class TestJSRuntime : JSRuntimeBase + internal class TestJSRuntime : JSRuntime { protected override void BeginInvokeJS(long asyncHandle, string identifier, string argsJson) { @@ -18,7 +18,7 @@ namespace Microsoft.JSInterop throw new NotImplementedException(); } - public static async Task WithJSRuntime(Action testCode) + public static async Task WithJSRuntime(Action testCode) { // Since the tests rely on the asynclocal JSRuntime.Current, ensure we // are on a distinct async context with a non-null JSRuntime.Current diff --git a/src/JSInterop/Mono.WebAssembly.Interop/ref/Mono.WebAssembly.Interop.netstandard2.0.cs b/src/JSInterop/Mono.WebAssembly.Interop/ref/Mono.WebAssembly.Interop.netstandard2.0.cs index 5299370576..2e4defd1b7 100644 --- a/src/JSInterop/Mono.WebAssembly.Interop/ref/Mono.WebAssembly.Interop.netstandard2.0.cs +++ b/src/JSInterop/Mono.WebAssembly.Interop/ref/Mono.WebAssembly.Interop.netstandard2.0.cs @@ -3,7 +3,7 @@ namespace Mono.WebAssembly.Interop { - public partial class MonoWebAssemblyJSRuntime : Microsoft.JSInterop.JSInProcessRuntimeBase + public partial class MonoWebAssemblyJSRuntime : Microsoft.JSInterop.JSInProcessRuntime { public MonoWebAssemblyJSRuntime() { } protected override void BeginInvokeJS(long asyncHandle, string identifier, string argsJson) { } diff --git a/src/JSInterop/Mono.WebAssembly.Interop/src/MonoWebAssemblyJSRuntime.cs b/src/JSInterop/Mono.WebAssembly.Interop/src/MonoWebAssemblyJSRuntime.cs index e65df172f8..0e292a3e3c 100644 --- a/src/JSInterop/Mono.WebAssembly.Interop/src/MonoWebAssemblyJSRuntime.cs +++ b/src/JSInterop/Mono.WebAssembly.Interop/src/MonoWebAssemblyJSRuntime.cs @@ -5,6 +5,7 @@ using System; using System.Runtime.ExceptionServices; using System.Text.Json; using Microsoft.JSInterop; +using Microsoft.JSInterop.Infrastructure; using WebAssembly.JSInterop; namespace Mono.WebAssembly.Interop @@ -13,7 +14,7 @@ namespace Mono.WebAssembly.Interop /// Provides methods for invoking JavaScript functions for applications running /// on the Mono WebAssembly runtime. /// - public class MonoWebAssemblyJSRuntime : JSInProcessRuntimeBase + public class MonoWebAssemblyJSRuntime : JSInProcessRuntime { /// protected override string InvokeJS(string identifier, string argsJson) @@ -37,7 +38,7 @@ namespace Mono.WebAssembly.Interop // Invoked via Mono's JS interop mechanism (invoke_method) private static void EndInvokeJS(string argsJson) - => DotNetDispatcher.EndInvoke(argsJson); + => DotNetDispatcher.EndInvokeJS(argsJson); // Invoked via Mono's JS interop mechanism (invoke_method) private static void BeginInvokeDotNet(string callId, string assemblyNameOrDotNetObjectId, string methodIdentifier, string argsJson) @@ -58,7 +59,7 @@ namespace Mono.WebAssembly.Interop assemblyName = assemblyNameOrDotNetObjectId; } - DotNetDispatcher.BeginInvoke(callId, assemblyName, methodIdentifier, dotNetObjectId, argsJson); + DotNetDispatcher.BeginInvokeDotNet(callId, assemblyName, methodIdentifier, dotNetObjectId, argsJson); } protected override void EndInvokeDotNet( From c717230b1350ba5ad408de9155f2e784dcf3dbac Mon Sep 17 00:00:00 2001 From: Justin Kotalik Date: Thu, 15 Aug 2019 09:12:53 -0700 Subject: [PATCH 0159/1101] Cleanup to skip/flaky attributes (dotnet/extensions#2186) \n\nCommit migrated from https://github.com/dotnet/extensions/commit/cfef5e07fb893b1c5a94566a0d053290f0c75382 --- src/Testing/src/TestPlatformHelper.cs | 2 +- .../src/xunit/ConditionalFactAttribute.cs | 2 +- .../src/xunit/ConditionalFactDiscoverer.cs | 2 +- .../src/xunit/ConditionalTheoryAttribute.cs | 2 +- .../src/xunit/ConditionalTheoryDiscoverer.cs | 2 +- src/Testing/src/xunit/DockerOnlyAttribute.cs | 2 +- ...vironmentVariableSkipConditionAttribute.cs | 2 +- src/Testing/src/xunit/FlakyAttribute.cs | 2 +- src/Testing/src/xunit/FlakyTestDiscoverer.cs | 2 +- .../xunit/FrameworkSkipConditionAttribute.cs | 2 +- src/Testing/src/xunit/IEnvironmentVariable.cs | 2 +- src/Testing/src/xunit/ITestCondition.cs | 2 +- .../src/xunit/MinimumOsVersionAttribute.cs | 2 +- .../src/xunit/OSSkipConditionAttribute.cs | 2 +- src/Testing/src/xunit/OperatingSystems.cs | 2 +- src/Testing/src/xunit/RuntimeFrameworks.cs | 2 +- src/Testing/src/xunit/SkipOnCIAttribute.cs | 43 ++++++++++++++++ src/Testing/src/xunit/SkipOnHelixAttribute.cs | 50 +++++++++++++++++++ src/Testing/src/xunit/SkippedTestCase.cs | 2 +- src/Testing/src/xunit/TestMethodExtensions.cs | 2 +- src/Testing/src/xunit/WindowsVersions.cs | 2 +- src/Testing/test/ConditionalFactTest.cs | 2 +- src/Testing/test/ConditionalTheoryTest.cs | 2 +- src/Testing/test/DockerTests.cs | 2 +- .../EnvironmentVariableSkipConditionTest.cs | 2 +- src/Testing/test/FlakyAttributeTest.cs | 4 +- .../test/OSSkipConditionAttributeTest.cs | 2 +- src/Testing/test/OSSkipConditionTest.cs | 2 +- src/Testing/test/SkipOnCITests.cs | 22 ++++++++ src/Testing/test/TestPlatformHelperTest.cs | 2 +- 30 files changed, 144 insertions(+), 27 deletions(-) create mode 100644 src/Testing/src/xunit/SkipOnCIAttribute.cs create mode 100644 src/Testing/src/xunit/SkipOnHelixAttribute.cs create mode 100644 src/Testing/test/SkipOnCITests.cs diff --git a/src/Testing/src/TestPlatformHelper.cs b/src/Testing/src/TestPlatformHelper.cs index 1a3f275c7e..2c13e08eb3 100644 --- a/src/Testing/src/TestPlatformHelper.cs +++ b/src/Testing/src/TestPlatformHelper.cs @@ -20,4 +20,4 @@ namespace Microsoft.AspNetCore.Testing public static bool IsMac => RuntimeInformation.IsOSPlatform(OSPlatform.OSX); } -} \ No newline at end of file +} diff --git a/src/Testing/src/xunit/ConditionalFactAttribute.cs b/src/Testing/src/xunit/ConditionalFactAttribute.cs index ce37df2e56..fdc108190a 100644 --- a/src/Testing/src/xunit/ConditionalFactAttribute.cs +++ b/src/Testing/src/xunit/ConditionalFactAttribute.cs @@ -5,7 +5,7 @@ using System; using Xunit; using Xunit.Sdk; -namespace Microsoft.AspNetCore.Testing.xunit +namespace Microsoft.AspNetCore.Testing { [AttributeUsage(AttributeTargets.Method, AllowMultiple = false)] [XunitTestCaseDiscoverer("Microsoft.AspNetCore.Testing.xunit." + nameof(ConditionalFactDiscoverer), "Microsoft.AspNetCore.Testing")] diff --git a/src/Testing/src/xunit/ConditionalFactDiscoverer.cs b/src/Testing/src/xunit/ConditionalFactDiscoverer.cs index cf49b29e5a..ce190376fc 100644 --- a/src/Testing/src/xunit/ConditionalFactDiscoverer.cs +++ b/src/Testing/src/xunit/ConditionalFactDiscoverer.cs @@ -4,7 +4,7 @@ using Xunit.Abstractions; using Xunit.Sdk; -namespace Microsoft.AspNetCore.Testing.xunit +namespace Microsoft.AspNetCore.Testing { internal class ConditionalFactDiscoverer : FactDiscoverer { diff --git a/src/Testing/src/xunit/ConditionalTheoryAttribute.cs b/src/Testing/src/xunit/ConditionalTheoryAttribute.cs index fe45f2ffc6..58b460e96e 100644 --- a/src/Testing/src/xunit/ConditionalTheoryAttribute.cs +++ b/src/Testing/src/xunit/ConditionalTheoryAttribute.cs @@ -5,7 +5,7 @@ using System; using Xunit; using Xunit.Sdk; -namespace Microsoft.AspNetCore.Testing.xunit +namespace Microsoft.AspNetCore.Testing { [AttributeUsage(AttributeTargets.Method, AllowMultiple = false)] [XunitTestCaseDiscoverer("Microsoft.AspNetCore.Testing.xunit." + nameof(ConditionalTheoryDiscoverer), "Microsoft.AspNetCore.Testing")] diff --git a/src/Testing/src/xunit/ConditionalTheoryDiscoverer.cs b/src/Testing/src/xunit/ConditionalTheoryDiscoverer.cs index 9e413cd580..c9ee58889a 100644 --- a/src/Testing/src/xunit/ConditionalTheoryDiscoverer.cs +++ b/src/Testing/src/xunit/ConditionalTheoryDiscoverer.cs @@ -5,7 +5,7 @@ using System.Collections.Generic; using Xunit.Abstractions; using Xunit.Sdk; -namespace Microsoft.AspNetCore.Testing.xunit +namespace Microsoft.AspNetCore.Testing { internal class ConditionalTheoryDiscoverer : TheoryDiscoverer { diff --git a/src/Testing/src/xunit/DockerOnlyAttribute.cs b/src/Testing/src/xunit/DockerOnlyAttribute.cs index d67a35a672..7d809884d6 100644 --- a/src/Testing/src/xunit/DockerOnlyAttribute.cs +++ b/src/Testing/src/xunit/DockerOnlyAttribute.cs @@ -6,7 +6,7 @@ using System.IO; using System.Linq; using System.Runtime.InteropServices; -namespace Microsoft.AspNetCore.Testing.xunit +namespace Microsoft.AspNetCore.Testing { [AttributeUsage(AttributeTargets.Method, Inherited = true, AllowMultiple = false)] public sealed class DockerOnlyAttribute : Attribute, ITestCondition diff --git a/src/Testing/src/xunit/EnvironmentVariableSkipConditionAttribute.cs b/src/Testing/src/xunit/EnvironmentVariableSkipConditionAttribute.cs index fe215a8e0b..0599e31901 100644 --- a/src/Testing/src/xunit/EnvironmentVariableSkipConditionAttribute.cs +++ b/src/Testing/src/xunit/EnvironmentVariableSkipConditionAttribute.cs @@ -4,7 +4,7 @@ using System; using System.Linq; -namespace Microsoft.AspNetCore.Testing.xunit +namespace Microsoft.AspNetCore.Testing { /// /// Skips a test when the value of an environment variable matches any of the supplied values. diff --git a/src/Testing/src/xunit/FlakyAttribute.cs b/src/Testing/src/xunit/FlakyAttribute.cs index ab4450e685..acea96f3cb 100644 --- a/src/Testing/src/xunit/FlakyAttribute.cs +++ b/src/Testing/src/xunit/FlakyAttribute.cs @@ -2,7 +2,7 @@ using System; using System.Collections.Generic; using Xunit.Sdk; -namespace Microsoft.AspNetCore.Testing.xunit +namespace Microsoft.AspNetCore.Testing { /// /// Marks a test as "Flaky" so that the build will sequester it and ignore failures. diff --git a/src/Testing/src/xunit/FlakyTestDiscoverer.cs b/src/Testing/src/xunit/FlakyTestDiscoverer.cs index 344b9b2378..aea2f9ea5b 100644 --- a/src/Testing/src/xunit/FlakyTestDiscoverer.cs +++ b/src/Testing/src/xunit/FlakyTestDiscoverer.cs @@ -4,7 +4,7 @@ using System.Linq; using Xunit.Abstractions; using Xunit.Sdk; -namespace Microsoft.AspNetCore.Testing.xunit +namespace Microsoft.AspNetCore.Testing { public class FlakyTestDiscoverer : ITraitDiscoverer { diff --git a/src/Testing/src/xunit/FrameworkSkipConditionAttribute.cs b/src/Testing/src/xunit/FrameworkSkipConditionAttribute.cs index 168076a434..b7719848a6 100644 --- a/src/Testing/src/xunit/FrameworkSkipConditionAttribute.cs +++ b/src/Testing/src/xunit/FrameworkSkipConditionAttribute.cs @@ -3,7 +3,7 @@ using System; -namespace Microsoft.AspNetCore.Testing.xunit +namespace Microsoft.AspNetCore.Testing { [AttributeUsage(AttributeTargets.Method, AllowMultiple = false)] public class FrameworkSkipConditionAttribute : Attribute, ITestCondition diff --git a/src/Testing/src/xunit/IEnvironmentVariable.cs b/src/Testing/src/xunit/IEnvironmentVariable.cs index 068c210611..ed06ed6505 100644 --- a/src/Testing/src/xunit/IEnvironmentVariable.cs +++ b/src/Testing/src/xunit/IEnvironmentVariable.cs @@ -1,7 +1,7 @@ // 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. -namespace Microsoft.AspNetCore.Testing.xunit +namespace Microsoft.AspNetCore.Testing { internal interface IEnvironmentVariable { diff --git a/src/Testing/src/xunit/ITestCondition.cs b/src/Testing/src/xunit/ITestCondition.cs index bb6ff1f031..34767b8574 100644 --- a/src/Testing/src/xunit/ITestCondition.cs +++ b/src/Testing/src/xunit/ITestCondition.cs @@ -1,7 +1,7 @@ // 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. -namespace Microsoft.AspNetCore.Testing.xunit +namespace Microsoft.AspNetCore.Testing { public interface ITestCondition { diff --git a/src/Testing/src/xunit/MinimumOsVersionAttribute.cs b/src/Testing/src/xunit/MinimumOsVersionAttribute.cs index 89e3b19556..df4985d338 100644 --- a/src/Testing/src/xunit/MinimumOsVersionAttribute.cs +++ b/src/Testing/src/xunit/MinimumOsVersionAttribute.cs @@ -5,7 +5,7 @@ using System; using System.Runtime.InteropServices; using Microsoft.Win32; -namespace Microsoft.AspNetCore.Testing.xunit +namespace Microsoft.AspNetCore.Testing { /// /// Skips a test if the OS is the given type (Windows) and the OS version is less than specified. diff --git a/src/Testing/src/xunit/OSSkipConditionAttribute.cs b/src/Testing/src/xunit/OSSkipConditionAttribute.cs index 9996510718..7655a3b45a 100644 --- a/src/Testing/src/xunit/OSSkipConditionAttribute.cs +++ b/src/Testing/src/xunit/OSSkipConditionAttribute.cs @@ -6,7 +6,7 @@ using System.Collections.Generic; using System.Linq; using System.Runtime.InteropServices; -namespace Microsoft.AspNetCore.Testing.xunit +namespace Microsoft.AspNetCore.Testing { [AttributeUsage(AttributeTargets.Method | AttributeTargets.Class | AttributeTargets.Assembly, AllowMultiple = true)] public class OSSkipConditionAttribute : Attribute, ITestCondition diff --git a/src/Testing/src/xunit/OperatingSystems.cs b/src/Testing/src/xunit/OperatingSystems.cs index c575d3e197..2ddacacab9 100644 --- a/src/Testing/src/xunit/OperatingSystems.cs +++ b/src/Testing/src/xunit/OperatingSystems.cs @@ -3,7 +3,7 @@ using System; -namespace Microsoft.AspNetCore.Testing.xunit +namespace Microsoft.AspNetCore.Testing { [Flags] public enum OperatingSystems diff --git a/src/Testing/src/xunit/RuntimeFrameworks.cs b/src/Testing/src/xunit/RuntimeFrameworks.cs index 2ec5ea7ec1..3a69022b88 100644 --- a/src/Testing/src/xunit/RuntimeFrameworks.cs +++ b/src/Testing/src/xunit/RuntimeFrameworks.cs @@ -3,7 +3,7 @@ using System; -namespace Microsoft.AspNetCore.Testing.xunit +namespace Microsoft.AspNetCore.Testing { [Flags] public enum RuntimeFrameworks diff --git a/src/Testing/src/xunit/SkipOnCIAttribute.cs b/src/Testing/src/xunit/SkipOnCIAttribute.cs new file mode 100644 index 0000000000..1ee0b8cde8 --- /dev/null +++ b/src/Testing/src/xunit/SkipOnCIAttribute.cs @@ -0,0 +1,43 @@ +// 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; + +namespace Microsoft.AspNetCore.Testing +{ + /// + /// Skip test if running on CI + /// + [AttributeUsage(AttributeTargets.Method | AttributeTargets.Class, AllowMultiple = false)] + public class SkipOnCIAttribute : Attribute, ITestCondition + { + public SkipOnCIAttribute(string issueUrl = "") + { + IssueUrl = issueUrl; + } + + public string IssueUrl { get; } + + public bool IsMet + { + get + { + return !OnCI(); + } + } + + public string SkipReason + { + get + { + return $"This test is skipped on CI"; + } + } + + public static bool OnCI() => OnHelix() || OnAzdo(); + public static bool OnHelix() => !string.IsNullOrEmpty(GetTargetHelixQueue()); + public static string GetTargetHelixQueue() => Environment.GetEnvironmentVariable("helix"); + public static bool OnAzdo() => !string.IsNullOrEmpty(GetIfOnAzdo()); + public static string GetIfOnAzdo() => Environment.GetEnvironmentVariable("AGENT_OS"); + } +} diff --git a/src/Testing/src/xunit/SkipOnHelixAttribute.cs b/src/Testing/src/xunit/SkipOnHelixAttribute.cs new file mode 100644 index 0000000000..85e82c1154 --- /dev/null +++ b/src/Testing/src/xunit/SkipOnHelixAttribute.cs @@ -0,0 +1,50 @@ +// 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.Linq; + +namespace Microsoft.AspNetCore.Testing +{ + /// + /// Skip test if running on helix (or a particular helix queue). + /// + [AttributeUsage(AttributeTargets.Method | AttributeTargets.Class, AllowMultiple = false)] + public class SkipOnHelixAttribute : Attribute, ITestCondition + { + public SkipOnHelixAttribute(string issueUrl) + { + if (string.IsNullOrEmpty(issueUrl)) + { + throw new ArgumentException(); + } + IssueUrl = issueUrl; + } + + public string IssueUrl { get; } + + public bool IsMet + { + get + { + var skip = OnHelix() && (Queues == null || Queues.ToLowerInvariant().Split(';').Contains(GetTargetHelixQueue().ToLowerInvariant())); + return !skip; + } + } + + // Queues that should be skipped on, i.e. "Windows.10.Amd64.ClientRS4.VS2017.Open;OSX.1012.Amd64.Open" + public string Queues { get; set; } + + public string SkipReason + { + get + { + return $"This test is skipped on helix"; + } + } + + public static bool OnHelix() => !string.IsNullOrEmpty(GetTargetHelixQueue()); + + public static string GetTargetHelixQueue() => Environment.GetEnvironmentVariable("helix"); + } +} diff --git a/src/Testing/src/xunit/SkippedTestCase.cs b/src/Testing/src/xunit/SkippedTestCase.cs index 1c25c507b9..b514c57209 100644 --- a/src/Testing/src/xunit/SkippedTestCase.cs +++ b/src/Testing/src/xunit/SkippedTestCase.cs @@ -5,7 +5,7 @@ using System; using Xunit.Abstractions; using Xunit.Sdk; -namespace Microsoft.AspNetCore.Testing.xunit +namespace Microsoft.AspNetCore.Testing { public class SkippedTestCase : XunitTestCase { diff --git a/src/Testing/src/xunit/TestMethodExtensions.cs b/src/Testing/src/xunit/TestMethodExtensions.cs index 5ec3bb4ec3..96dd93eb7c 100644 --- a/src/Testing/src/xunit/TestMethodExtensions.cs +++ b/src/Testing/src/xunit/TestMethodExtensions.cs @@ -5,7 +5,7 @@ using System.Linq; using Xunit.Abstractions; using Xunit.Sdk; -namespace Microsoft.AspNetCore.Testing.xunit +namespace Microsoft.AspNetCore.Testing { public static class TestMethodExtensions { diff --git a/src/Testing/src/xunit/WindowsVersions.cs b/src/Testing/src/xunit/WindowsVersions.cs index ff8312b363..d89da44de3 100644 --- a/src/Testing/src/xunit/WindowsVersions.cs +++ b/src/Testing/src/xunit/WindowsVersions.cs @@ -1,7 +1,7 @@ // 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. -namespace Microsoft.AspNetCore.Testing.xunit +namespace Microsoft.AspNetCore.Testing { public static class WindowsVersions { diff --git a/src/Testing/test/ConditionalFactTest.cs b/src/Testing/test/ConditionalFactTest.cs index 9c5c6d037d..efc3a16dea 100644 --- a/src/Testing/test/ConditionalFactTest.cs +++ b/src/Testing/test/ConditionalFactTest.cs @@ -2,7 +2,7 @@ // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; -using Microsoft.AspNetCore.Testing.xunit; +using Microsoft.AspNetCore.Testing; using Xunit; namespace Microsoft.AspNetCore.Testing diff --git a/src/Testing/test/ConditionalTheoryTest.cs b/src/Testing/test/ConditionalTheoryTest.cs index d824eb61b4..07cf6a968f 100644 --- a/src/Testing/test/ConditionalTheoryTest.cs +++ b/src/Testing/test/ConditionalTheoryTest.cs @@ -2,7 +2,7 @@ // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; -using Microsoft.AspNetCore.Testing.xunit; +using Microsoft.AspNetCore.Testing; using Xunit; using Xunit.Abstractions; diff --git a/src/Testing/test/DockerTests.cs b/src/Testing/test/DockerTests.cs index c66fdd679c..12735057d3 100644 --- a/src/Testing/test/DockerTests.cs +++ b/src/Testing/test/DockerTests.cs @@ -3,7 +3,7 @@ using System; using System.Runtime.InteropServices; -using Microsoft.AspNetCore.Testing.xunit; +using Microsoft.AspNetCore.Testing; using Xunit; namespace Microsoft.AspNetCore.Testing diff --git a/src/Testing/test/EnvironmentVariableSkipConditionTest.cs b/src/Testing/test/EnvironmentVariableSkipConditionTest.cs index d5e7b6342b..cbc8e9adad 100644 --- a/src/Testing/test/EnvironmentVariableSkipConditionTest.cs +++ b/src/Testing/test/EnvironmentVariableSkipConditionTest.cs @@ -3,7 +3,7 @@ using Xunit; -namespace Microsoft.AspNetCore.Testing.xunit +namespace Microsoft.AspNetCore.Testing { public class EnvironmentVariableSkipConditionTest { diff --git a/src/Testing/test/FlakyAttributeTest.cs b/src/Testing/test/FlakyAttributeTest.cs index 1b9a122d93..ae06e5cf50 100644 --- a/src/Testing/test/FlakyAttributeTest.cs +++ b/src/Testing/test/FlakyAttributeTest.cs @@ -1,4 +1,6 @@ -using Microsoft.AspNetCore.Testing.xunit; +// 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.Collections.Generic; using Xunit; diff --git a/src/Testing/test/OSSkipConditionAttributeTest.cs b/src/Testing/test/OSSkipConditionAttributeTest.cs index 0120eb7a4c..199af3ab6e 100644 --- a/src/Testing/test/OSSkipConditionAttributeTest.cs +++ b/src/Testing/test/OSSkipConditionAttributeTest.cs @@ -5,7 +5,7 @@ using System; using System.Runtime.InteropServices; using Xunit; -namespace Microsoft.AspNetCore.Testing.xunit +namespace Microsoft.AspNetCore.Testing { public class OSSkipConditionAttributeTest { diff --git a/src/Testing/test/OSSkipConditionTest.cs b/src/Testing/test/OSSkipConditionTest.cs index 2d76f2c2cd..a7904b1730 100644 --- a/src/Testing/test/OSSkipConditionTest.cs +++ b/src/Testing/test/OSSkipConditionTest.cs @@ -5,7 +5,7 @@ using System; using System.Runtime.InteropServices; using Xunit; -namespace Microsoft.AspNetCore.Testing.xunit +namespace Microsoft.AspNetCore.Testing { public class OSSkipConditionTest { diff --git a/src/Testing/test/SkipOnCITests.cs b/src/Testing/test/SkipOnCITests.cs new file mode 100644 index 0000000000..8df5e73c30 --- /dev/null +++ b/src/Testing/test/SkipOnCITests.cs @@ -0,0 +1,22 @@ +// 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 Microsoft.AspNetCore.Testing; +using Xunit; + +namespace Microsoft.AspNetCore.Testing.Tests +{ + public class SkipOnCITests + { + [ConditionalFact] + [SkipOnCI] + public void AlwaysSkipOnCI() + { + if (!string.IsNullOrEmpty(Environment.GetEnvironmentVariable("HELIX")) || !string.IsNullOrEmpty(Environment.GetEnvironmentVariable("AGENT_OS"))) + { + throw new Exception("Flaky!"); + } + } + } +} diff --git a/src/Testing/test/TestPlatformHelperTest.cs b/src/Testing/test/TestPlatformHelperTest.cs index 8e35e164d5..b1c2fbf2f8 100644 --- a/src/Testing/test/TestPlatformHelperTest.cs +++ b/src/Testing/test/TestPlatformHelperTest.cs @@ -1,7 +1,7 @@ // 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 Microsoft.AspNetCore.Testing.xunit; +using Microsoft.AspNetCore.Testing; using Xunit; namespace Microsoft.AspNetCore.Testing From ce392fa4f7a2e6b8eb186d64e0e6eb7d07c3a2c4 Mon Sep 17 00:00:00 2001 From: Justin Kotalik Date: Thu, 15 Aug 2019 09:12:53 -0700 Subject: [PATCH 0160/1101] Cleanup to skip/flaky attributes (#2186) --- src/Testing/test/LoggedTestXunitTests.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Testing/test/LoggedTestXunitTests.cs b/src/Testing/test/LoggedTestXunitTests.cs index 520ffaaa9e..ab9ee746c3 100644 --- a/src/Testing/test/LoggedTestXunitTests.cs +++ b/src/Testing/test/LoggedTestXunitTests.cs @@ -3,7 +3,7 @@ using System.Linq; using System.Reflection; -using Microsoft.AspNetCore.Testing.xunit; +using Microsoft.AspNetCore.Testing; using Microsoft.Extensions.DependencyInjection; using Xunit; using Xunit.Abstractions; From 3d9311a29f5900cd4fe34d12a0c99039a5f629cb Mon Sep 17 00:00:00 2001 From: Brennan Date: Thu, 15 Aug 2019 14:09:05 -0700 Subject: [PATCH 0161/1101] Remove value span usage (#12990) --- .../common/Http.Connections.Common/src/NegotiateProtocol.cs | 6 ++---- .../common/Http.Connections/test/NegotiateProtocolTests.cs | 1 + 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/src/SignalR/common/Http.Connections.Common/src/NegotiateProtocol.cs b/src/SignalR/common/Http.Connections.Common/src/NegotiateProtocol.cs index a23a9d6c0b..93035f0613 100644 --- a/src/SignalR/common/Http.Connections.Common/src/NegotiateProtocol.cs +++ b/src/SignalR/common/Http.Connections.Common/src/NegotiateProtocol.cs @@ -222,13 +222,11 @@ namespace Microsoft.AspNetCore.Http.Connections switch (reader.TokenType) { case JsonTokenType.PropertyName: - var memberName = reader.ValueSpan; - - if (memberName.SequenceEqual(TransportPropertyNameBytes.EncodedUtf8Bytes)) + if (reader.ValueTextEquals(TransportPropertyNameBytes.EncodedUtf8Bytes)) { availableTransport.Transport = reader.ReadAsString(TransportPropertyName); } - else if (memberName.SequenceEqual(TransferFormatsPropertyNameBytes.EncodedUtf8Bytes)) + else if (reader.ValueTextEquals(TransferFormatsPropertyNameBytes.EncodedUtf8Bytes)) { reader.CheckRead(); reader.EnsureArrayStart(); diff --git a/src/SignalR/common/Http.Connections/test/NegotiateProtocolTests.cs b/src/SignalR/common/Http.Connections/test/NegotiateProtocolTests.cs index e92d3c3b42..ed6a1d1a2d 100644 --- a/src/SignalR/common/Http.Connections/test/NegotiateProtocolTests.cs +++ b/src/SignalR/common/Http.Connections/test/NegotiateProtocolTests.cs @@ -18,6 +18,7 @@ namespace Microsoft.AspNetCore.Http.Connections.Tests [InlineData("{\"url\": \"http://foo.com/chat\"}", null, null, "http://foo.com/chat", null)] [InlineData("{\"url\": \"http://foo.com/chat\", \"accessToken\": \"token\"}", null, null, "http://foo.com/chat", "token")] [InlineData("{\"connectionId\":\"123\",\"availableTransports\":[{\"transport\":\"test\",\"transferFormats\":[]}]}", "123", new[] { "test" }, null, null)] + [InlineData("{\"connectionId\":\"123\",\"availableTransports\":[{\"\\u0074ransport\":\"test\",\"transferFormats\":[]}]}", "123", new[] { "test" }, null, null)] public void ParsingNegotiateResponseMessageSuccessForValid(string json, string connectionId, string[] availableTransports, string url, string accessToken) { var responseData = Encoding.UTF8.GetBytes(json); From 6b4a101ca76ce7c4a1eb5efb8478dd438c32a4ad Mon Sep 17 00:00:00 2001 From: Stafford Williams Date: Fri, 16 Aug 2019 09:19:37 +1000 Subject: [PATCH 0162/1101] Crankier server (#12406) * move server into Crankier * added ConnectionCounter * allow logging --- .../Crankier/Commands/Defaults.cs | 2 + .../Crankier/Commands/ServerCommand.cs | 60 ++++++++++++++ .../benchmarkapps/Crankier/Crankier.csproj | 5 ++ .../perf/benchmarkapps/Crankier/Program.cs | 1 + .../Crankier/Server/ConnectionCounter.cs | 60 ++++++++++++++ .../Server/ConnectionCounterHostedService.cs | 79 +++++++++++++++++++ .../Crankier/Server/ConnectionSummary.cs | 18 +++++ .../benchmarkapps/Crankier/Server/EchoHub.cs | 72 +++++++++++++++++ .../benchmarkapps/Crankier/Server/Startup.cs | 39 +++++++++ 9 files changed, 336 insertions(+) create mode 100644 src/SignalR/perf/benchmarkapps/Crankier/Commands/ServerCommand.cs create mode 100644 src/SignalR/perf/benchmarkapps/Crankier/Server/ConnectionCounter.cs create mode 100644 src/SignalR/perf/benchmarkapps/Crankier/Server/ConnectionCounterHostedService.cs create mode 100644 src/SignalR/perf/benchmarkapps/Crankier/Server/ConnectionSummary.cs create mode 100644 src/SignalR/perf/benchmarkapps/Crankier/Server/EchoHub.cs create mode 100644 src/SignalR/perf/benchmarkapps/Crankier/Server/Startup.cs diff --git a/src/SignalR/perf/benchmarkapps/Crankier/Commands/Defaults.cs b/src/SignalR/perf/benchmarkapps/Crankier/Commands/Defaults.cs index 732ccb6af3..84027fff16 100644 --- a/src/SignalR/perf/benchmarkapps/Crankier/Commands/Defaults.cs +++ b/src/SignalR/perf/benchmarkapps/Crankier/Commands/Defaults.cs @@ -2,6 +2,7 @@ // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using Microsoft.AspNetCore.Http.Connections; +using Microsoft.Extensions.Logging; namespace Microsoft.AspNetCore.SignalR.Crankier.Commands { @@ -11,5 +12,6 @@ namespace Microsoft.AspNetCore.SignalR.Crankier.Commands public static readonly int NumberOfConnections = 10_000; public static readonly int SendDurationInSeconds = 300; public static readonly HttpTransportType TransportType = HttpTransportType.WebSockets; + public static readonly LogLevel LogLevel = LogLevel.None; } } diff --git a/src/SignalR/perf/benchmarkapps/Crankier/Commands/ServerCommand.cs b/src/SignalR/perf/benchmarkapps/Crankier/Commands/ServerCommand.cs new file mode 100644 index 0000000000..cb1ef585b6 --- /dev/null +++ b/src/SignalR/perf/benchmarkapps/Crankier/Commands/ServerCommand.cs @@ -0,0 +1,60 @@ +// 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.Threading.Tasks; +using Microsoft.AspNetCore.Http.Connections; +using Microsoft.Extensions.CommandLineUtils; +using static Microsoft.AspNetCore.SignalR.Crankier.Commands.CommandLineUtilities; +using System.Diagnostics; +using Microsoft.AspNetCore.Hosting; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.Logging; +using Microsoft.AspNetCore.SignalR.Crankier.Server; + +namespace Microsoft.AspNetCore.SignalR.Crankier.Commands +{ + internal class ServerCommand + { + public static void Register(CommandLineApplication app) + { + app.Command("server", cmd => + { + var logLevelOption = cmd.Option("--log ", "The LogLevel to use.", CommandOptionType.SingleValue); + + cmd.OnExecute(() => + { + LogLevel logLevel = Defaults.LogLevel; + + if (logLevelOption.HasValue() && !Enum.TryParse(logLevelOption.Value(), out logLevel)) + { + return InvalidArg(logLevelOption); + } + return Execute(logLevel); + }); + }); + } + + private static int Execute(LogLevel logLevel) + { + Console.WriteLine($"Process ID: {Process.GetCurrentProcess().Id}"); + + var config = new ConfigurationBuilder() + .AddEnvironmentVariables(prefix: "ASPNETCORE_") + .Build(); + + var host = new WebHostBuilder() + .UseConfiguration(config) + .ConfigureLogging(loggerFactory => + { + loggerFactory.AddConsole().SetMinimumLevel(logLevel); + }) + .UseKestrel() + .UseStartup(); + + host.Build().Run(); + + return 0; + } + } +} diff --git a/src/SignalR/perf/benchmarkapps/Crankier/Crankier.csproj b/src/SignalR/perf/benchmarkapps/Crankier/Crankier.csproj index 82ae59f1ef..552cb4527d 100644 --- a/src/SignalR/perf/benchmarkapps/Crankier/Crankier.csproj +++ b/src/SignalR/perf/benchmarkapps/Crankier/Crankier.csproj @@ -9,6 +9,11 @@ + + + + + diff --git a/src/SignalR/perf/benchmarkapps/Crankier/Program.cs b/src/SignalR/perf/benchmarkapps/Crankier/Program.cs index a3443ecd94..93f53ea328 100644 --- a/src/SignalR/perf/benchmarkapps/Crankier/Program.cs +++ b/src/SignalR/perf/benchmarkapps/Crankier/Program.cs @@ -30,6 +30,7 @@ namespace Microsoft.AspNetCore.SignalR.Crankier LocalCommand.Register(app); AgentCommand.Register(app); WorkerCommand.Register(app); + ServerCommand.Register(app); app.Command("help", cmd => { diff --git a/src/SignalR/perf/benchmarkapps/Crankier/Server/ConnectionCounter.cs b/src/SignalR/perf/benchmarkapps/Crankier/Server/ConnectionCounter.cs new file mode 100644 index 0000000000..1ab6a25abc --- /dev/null +++ b/src/SignalR/perf/benchmarkapps/Crankier/Server/ConnectionCounter.cs @@ -0,0 +1,60 @@ +// 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; + +namespace Microsoft.AspNetCore.SignalR.Crankier.Server +{ + public class ConnectionCounter + { + private int _totalConnectedCount; + private int _peakConnectedCount; + private int _totalDisconnectedCount; + private int _receivedCount; + + private readonly object _lock = new object(); + + public ConnectionSummary Summary + { + get + { + lock (_lock) + { + return new ConnectionSummary + { + CurrentConnections = _totalConnectedCount - _totalDisconnectedCount, + PeakConnections = _peakConnectedCount, + TotalConnected = _totalConnectedCount, + TotalDisconnected = _totalDisconnectedCount, + ReceivedCount = _receivedCount + }; + } + } + } + + public void Receive(string payload) + { + lock (_lock) + { + _receivedCount += payload.Length; + } + } + + public void Connected() + { + lock (_lock) + { + _totalConnectedCount++; + _peakConnectedCount = Math.Max(_totalConnectedCount - _totalDisconnectedCount, _peakConnectedCount); + } + } + + public void Disconnected() + { + lock (_lock) + { + _totalDisconnectedCount++; + } + } + } +} \ No newline at end of file diff --git a/src/SignalR/perf/benchmarkapps/Crankier/Server/ConnectionCounterHostedService.cs b/src/SignalR/perf/benchmarkapps/Crankier/Server/ConnectionCounterHostedService.cs new file mode 100644 index 0000000000..44b8bb26f2 --- /dev/null +++ b/src/SignalR/perf/benchmarkapps/Crankier/Server/ConnectionCounterHostedService.cs @@ -0,0 +1,79 @@ +// 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.Diagnostics; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.Extensions.Hosting; + +namespace Microsoft.AspNetCore.SignalR.Crankier.Server +{ + public class ConnectionCounterHostedService : IHostedService, IDisposable + { + private Stopwatch _timeSinceFirstConnection; + private readonly ConnectionCounter _counter; + private ConnectionSummary _lastSummary; + private Timer _timer; + private int _executingDoWork; + + public ConnectionCounterHostedService(ConnectionCounter counter) + { + _counter = counter; + _timeSinceFirstConnection = new Stopwatch(); + } + + public Task StartAsync(CancellationToken cancellationToken) + { + _timer = new Timer(DoWork, null, TimeSpan.Zero, TimeSpan.FromSeconds(1)); + + return Task.CompletedTask; + } + + private void DoWork(object state) + { + if (Interlocked.Exchange(ref _executingDoWork, 1) == 0) + { + var summary = _counter.Summary; + + if (summary.PeakConnections > 0) + { + if (_timeSinceFirstConnection.ElapsedTicks == 0) + { + _timeSinceFirstConnection.Start(); + } + + var elapsed = _timeSinceFirstConnection.Elapsed; + + if (_lastSummary != null) + { + Console.WriteLine(@"[{0:hh\:mm\:ss}] Current: {1}, peak: {2}, connected: {3}, disconnected: {4}, rate: {5}/s", + elapsed, + summary.CurrentConnections, + summary.PeakConnections, + summary.TotalConnected - _lastSummary.TotalConnected, + summary.TotalDisconnected - _lastSummary.TotalDisconnected, + summary.CurrentConnections - _lastSummary.CurrentConnections + ); + } + + _lastSummary = summary; + } + + Interlocked.Exchange(ref _executingDoWork, 0); + } + } + + public Task StopAsync(CancellationToken cancellationToken) + { + _timer?.Change(Timeout.Infinite, 0); + + return Task.CompletedTask; + } + + public void Dispose() + { + _timer?.Dispose(); + } + } +} \ No newline at end of file diff --git a/src/SignalR/perf/benchmarkapps/Crankier/Server/ConnectionSummary.cs b/src/SignalR/perf/benchmarkapps/Crankier/Server/ConnectionSummary.cs new file mode 100644 index 0000000000..83f38aaf62 --- /dev/null +++ b/src/SignalR/perf/benchmarkapps/Crankier/Server/ConnectionSummary.cs @@ -0,0 +1,18 @@ +// 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. + +namespace Microsoft.AspNetCore.SignalR.Crankier.Server +{ + public class ConnectionSummary + { + public int TotalConnected { get; set; } + + public int TotalDisconnected { get; set; } + + public int PeakConnections { get; set; } + + public int CurrentConnections { get; set; } + + public int ReceivedCount { get; set; } + } +} \ No newline at end of file diff --git a/src/SignalR/perf/benchmarkapps/Crankier/Server/EchoHub.cs b/src/SignalR/perf/benchmarkapps/Crankier/Server/EchoHub.cs new file mode 100644 index 0000000000..0b24b46e9b --- /dev/null +++ b/src/SignalR/perf/benchmarkapps/Crankier/Server/EchoHub.cs @@ -0,0 +1,72 @@ +// 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.Threading; +using System.Threading.Tasks; +using Microsoft.AspNetCore.SignalR; + +namespace Microsoft.AspNetCore.SignalR.Crankier.Server +{ + public class EchoHub : Hub + { + private ConnectionCounter _counter; + + public EchoHub(ConnectionCounter counter) + { + _counter = counter; + } + + public async Task Broadcast(int duration) + { + var sent = 0; + try + { + var t = new CancellationTokenSource(); + t.CancelAfter(TimeSpan.FromSeconds(duration)); + while (!t.IsCancellationRequested && !Context.ConnectionAborted.IsCancellationRequested) + { + await Clients.All.SendAsync("send", DateTime.UtcNow); + sent++; + } + } + catch (Exception e) + { + Console.WriteLine(e); + } + Console.WriteLine("Broadcast exited: Sent {0} messages", sent); + } + + public override Task OnConnectedAsync() + { + _counter?.Connected(); + return Task.CompletedTask; + } + + public override Task OnDisconnectedAsync(Exception exception) + { + _counter?.Disconnected(); + return Task.CompletedTask; + } + + public DateTime Echo(DateTime time) + { + return time; + } + + public Task EchoAll(DateTime time) + { + return Clients.All.SendAsync("send", time); + } + + public void SendPayload(string payload) + { + _counter?.Receive(payload); + } + + public DateTime GetCurrentTime() + { + return DateTime.UtcNow; + } + } +} diff --git a/src/SignalR/perf/benchmarkapps/Crankier/Server/Startup.cs b/src/SignalR/perf/benchmarkapps/Crankier/Server/Startup.cs new file mode 100644 index 0000000000..d8d5efc5bf --- /dev/null +++ b/src/SignalR/perf/benchmarkapps/Crankier/Server/Startup.cs @@ -0,0 +1,39 @@ +// 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 Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.Hosting; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; + +namespace Microsoft.AspNetCore.SignalR.Crankier.Server +{ + public class Startup + { + private readonly IConfiguration _config; + public Startup(IConfiguration configuration) + { + _config = configuration; + } + + public void ConfigureServices(IServiceCollection services) + { + var signalrBuilder = services.AddSignalR() + .AddMessagePackProtocol(); + + services.AddSingleton(); + + services.AddHostedService(); + } + + public void Configure(IApplicationBuilder app, IWebHostEnvironment env) + { + app.UseRouting(); + + app.UseEndpoints(endpoints => + { + endpoints.MapHub("/echo"); + }); + } + } +} From 7795537181bf5fd4f4855a7846bee29c6a0fcc1c Mon Sep 17 00:00:00 2001 From: Brennan Date: Thu, 15 Aug 2019 16:30:07 -0700 Subject: [PATCH 0163/1101] Use relaxed encoder for Json (#12589) --- .../src/Protocol/JsonHubProtocol.cs | 42 ++++----- .../common/Shared/ReusableUtf8JsonWriter.cs | 9 +- .../Internal/Protocol/JsonHubProtocolTests.cs | 85 +------------------ .../Protocol/JsonHubProtocolTestsBase.cs | 46 +++++++++- .../NewtonsoftJsonHubProtocolTests.cs | 23 +---- .../Protocol/Utf8BufferTextWriterTests.cs | 4 +- 6 files changed, 75 insertions(+), 134 deletions(-) diff --git a/src/SignalR/common/Protocols.Json/src/Protocol/JsonHubProtocol.cs b/src/SignalR/common/Protocols.Json/src/Protocol/JsonHubProtocol.cs index 884e427b68..28596c6210 100644 --- a/src/SignalR/common/Protocols.Json/src/Protocol/JsonHubProtocol.cs +++ b/src/SignalR/common/Protocols.Json/src/Protocol/JsonHubProtocol.cs @@ -7,6 +7,7 @@ using System.Collections.Generic; using System.Diagnostics; using System.IO; using System.Runtime.ExceptionServices; +using System.Text.Encodings.Web; using System.Text.Json; using Microsoft.AspNetCore.Connections; using Microsoft.AspNetCore.Internal; @@ -551,19 +552,7 @@ namespace Microsoft.AspNetCore.SignalR.Protocol writer.WriteStartArray(ArgumentsPropertyNameBytes); foreach (var argument in arguments) { - var type = argument?.GetType(); - if (type == typeof(DateTime)) - { - writer.WriteStringValue((DateTime)argument); - } - else if (type == typeof(DateTimeOffset)) - { - writer.WriteStringValue((DateTimeOffset)argument); - } - else - { - JsonSerializer.Serialize(writer, argument, type, _payloadSerializerOptions); - } + JsonSerializer.Serialize(writer, argument, argument?.GetType(), _payloadSerializerOptions); } writer.WriteEndArray(); } @@ -746,19 +735,20 @@ namespace Microsoft.AspNetCore.SignalR.Protocol internal static JsonSerializerOptions CreateDefaultSerializerSettings() { - var options = new JsonSerializerOptions(); - options.WriteIndented = false; - options.ReadCommentHandling = JsonCommentHandling.Disallow; - options.AllowTrailingCommas = false; - options.IgnoreNullValues = false; - options.IgnoreReadOnlyProperties = false; - options.PropertyNamingPolicy = JsonNamingPolicy.CamelCase; - options.PropertyNameCaseInsensitive = true; - options.MaxDepth = 64; - options.DictionaryKeyPolicy = null; - options.DefaultBufferSize = 16 * 1024; - - return options; + return new JsonSerializerOptions() + { + WriteIndented = false, + ReadCommentHandling = JsonCommentHandling.Disallow, + AllowTrailingCommas = false, + IgnoreNullValues = false, + IgnoreReadOnlyProperties = false, + PropertyNamingPolicy = JsonNamingPolicy.CamelCase, + PropertyNameCaseInsensitive = true, + MaxDepth = 64, + DictionaryKeyPolicy = null, + DefaultBufferSize = 16 * 1024, + Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping, + }; } } } diff --git a/src/SignalR/common/Shared/ReusableUtf8JsonWriter.cs b/src/SignalR/common/Shared/ReusableUtf8JsonWriter.cs index 1dc980d750..c05c0397e6 100644 --- a/src/SignalR/common/Shared/ReusableUtf8JsonWriter.cs +++ b/src/SignalR/common/Shared/ReusableUtf8JsonWriter.cs @@ -3,6 +3,7 @@ using System; using System.Buffers; +using System.Text.Encodings.Web; using System.Text.Json; namespace Microsoft.AspNetCore.Internal @@ -20,7 +21,13 @@ namespace Microsoft.AspNetCore.Internal public ReusableUtf8JsonWriter(IBufferWriter stream) { - _writer = new Utf8JsonWriter(stream, new JsonWriterOptions() { SkipValidation = true }); + _writer = new Utf8JsonWriter(stream, new JsonWriterOptions() + { +#if !DEBUG + SkipValidation = true, +#endif + Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping + }); } public static ReusableUtf8JsonWriter Get(IBufferWriter stream) diff --git a/src/SignalR/common/SignalR.Common/test/Internal/Protocol/JsonHubProtocolTests.cs b/src/SignalR/common/SignalR.Common/test/Internal/Protocol/JsonHubProtocolTests.cs index a2f696ab17..6f9ba5cb60 100644 --- a/src/SignalR/common/SignalR.Common/test/Internal/Protocol/JsonHubProtocolTests.cs +++ b/src/SignalR/common/SignalR.Common/test/Internal/Protocol/JsonHubProtocolTests.cs @@ -7,6 +7,7 @@ using System.Collections.Generic; using System.IO; using System.Linq; using System.Text; +using System.Text.Encodings.Web; using System.Text.Json; using Microsoft.AspNetCore.Internal; using Microsoft.AspNetCore.SignalR.Protocol; @@ -28,7 +29,8 @@ namespace Microsoft.AspNetCore.SignalR.Common.Tests.Internal.Protocol PayloadSerializerOptions = new JsonSerializerOptions() { IgnoreNullValues = ignoreNullValues, - PropertyNamingPolicy = useCamelCase ? JsonNamingPolicy.CamelCase : null + PropertyNamingPolicy = useCamelCase ? JsonNamingPolicy.CamelCase : null, + Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping, } }; @@ -39,6 +41,7 @@ namespace Microsoft.AspNetCore.SignalR.Common.Tests.Internal.Protocol [InlineData("", "Error reading JSON.")] [InlineData("42", "Unexpected JSON Token Type 'Number'. Expected a JSON Object.")] [InlineData("{\"type\":\"foo\"}", "Expected 'type' to be of type Number.")] + [InlineData("{\"type\":3,\"invocationId\":\"42\",\"result\":true", "Error reading JSON.")] public void CustomInvalidMessages(string input, string expectedMessage) { input = Frame(input); @@ -101,98 +104,18 @@ namespace Microsoft.AspNetCore.SignalR.Common.Tests.Internal.Protocol Assert.Equal(expectedMessage, message); } - [Fact] - public void ReadCaseInsensitivePropertiesByDefault() - { - var input = Frame("{\"type\":2,\"invocationId\":\"123\",\"item\":{\"StrIngProp\":\"test\",\"DoublePrOp\":3.14159,\"IntProp\":43,\"DateTimeProp\":\"2019-06-03T22:00:00\",\"NuLLProp\":null,\"ByteARRProp\":\"AgQG\"}}"); - - var binder = new TestBinder(null, typeof(TemporaryCustomObject)); - var data = new ReadOnlySequence(Encoding.UTF8.GetBytes(input)); - JsonHubProtocol.TryParseMessage(ref data, binder, out var message); - - var streamItemMessage = Assert.IsType(message); - Assert.Equal(new TemporaryCustomObject() - { - ByteArrProp = new byte[] { 2, 4, 6 }, - IntProp = 43, - DoubleProp = 3.14159, - StringProp = "test", - DateTimeProp = DateTime.Parse("6/3/2019 10:00:00 PM") - }, streamItemMessage.Item); - } - public static IDictionary CustomProtocolTestData => new[] { new JsonProtocolTestData("InvocationMessage_HasFloatArgument", new InvocationMessage(null, "Target", new object[] { 1, "Foo", 2.0f }), true, true, "{\"type\":1,\"target\":\"Target\",\"arguments\":[1,\"Foo\",2]}"), - new JsonProtocolTestData("InvocationMessage_StringIsoDateArgument", new InvocationMessage("Method", new object[] { "2016-05-10T13:51:20+12:34" }), true, true, "{\"type\":1,\"target\":\"Method\",\"arguments\":[\"2016-05-10T13:51:20\\u002B12:34\"]}"), - new JsonProtocolTestData("InvocationMessage_HasCustomArgumentWithNoCamelCase", new InvocationMessage(null, "Target", new object[] { new TemporaryCustomObject() { ByteArrProp = new byte[] { 1, 2, 3 } } }), false, true, "{\"type\":1,\"target\":\"Target\",\"arguments\":[{\"StringProp\":\"SignalR!\",\"DoubleProp\":6.2831853071,\"IntProp\":42,\"DateTimeProp\":\"2017-04-11T00:00:00Z\",\"ByteArrProp\":\"AQID\"}]}"), - new JsonProtocolTestData("InvocationMessage_HasCustomArgumentWithNullValueIgnore", new InvocationMessage(null, "Target", new object[] { new TemporaryCustomObject() { ByteArrProp = new byte[] { 1, 2, 3 } } }), true, true, "{\"type\":1,\"target\":\"Target\",\"arguments\":[{\"stringProp\":\"SignalR!\",\"doubleProp\":6.2831853071,\"intProp\":42,\"dateTimeProp\":\"2017-04-11T00:00:00Z\",\"byteArrProp\":\"AQID\"}]}"), - new JsonProtocolTestData("InvocationMessage_HasCustomArgumentWithNullValueIgnoreAndNoCamelCase", new InvocationMessage(null, "Target", new object[] { new TemporaryCustomObject() { ByteArrProp = new byte[] { 1, 2, 3 } } }), false, false, "{\"type\":1,\"target\":\"Target\",\"arguments\":[{\"StringProp\":\"SignalR!\",\"DoubleProp\":6.2831853071,\"IntProp\":42,\"DateTimeProp\":\"2017-04-11T00:00:00Z\",\"NullProp\":null,\"ByteArrProp\":\"AQID\"}]}"), - new JsonProtocolTestData("InvocationMessage_HasCustomArgumentWithNullValueInclude", new InvocationMessage(null, "Target", new object[] { new TemporaryCustomObject() { ByteArrProp = new byte[] { 1, 2, 3 } } }), true, false, "{\"type\":1,\"target\":\"Target\",\"arguments\":[{\"stringProp\":\"SignalR!\",\"doubleProp\":6.2831853071,\"intProp\":42,\"dateTimeProp\":\"2017-04-11T00:00:00Z\",\"nullProp\":null,\"byteArrProp\":\"AQID\"}]}"), new JsonProtocolTestData("InvocationMessage_HasHeaders", AddHeaders(TestHeaders, new InvocationMessage("123", "Target", new object[] { 1, "Foo", 2.0f })), true, true, "{\"type\":1," + SerializedHeaders + ",\"invocationId\":\"123\",\"target\":\"Target\",\"arguments\":[1,\"Foo\",2]}"), - new JsonProtocolTestData("StreamItemMessage_HasCustomItemWithNoCamelCase", new StreamItemMessage("123", new TemporaryCustomObject() { ByteArrProp = new byte[] { 1, 2, 3 } }), false, true, "{\"type\":2,\"invocationId\":\"123\",\"item\":{\"StringProp\":\"SignalR!\",\"DoubleProp\":6.2831853071,\"IntProp\":42,\"DateTimeProp\":\"2017-04-11T00:00:00Z\",\"ByteArrProp\":\"AQID\"}}"), - new JsonProtocolTestData("StreamItemMessage_HasCustomItemWithNullValueIgnore", new StreamItemMessage("123", new TemporaryCustomObject() { ByteArrProp = new byte[] { 1, 2, 3 } }), true, true, "{\"type\":2,\"invocationId\":\"123\",\"item\":{\"stringProp\":\"SignalR!\",\"doubleProp\":6.2831853071,\"intProp\":42,\"dateTimeProp\":\"2017-04-11T00:00:00Z\",\"byteArrProp\":\"AQID\"}}"), - new JsonProtocolTestData("StreamItemMessage_HasCustomItemWithNullValueIgnoreAndNoCamelCase", new StreamItemMessage("123", new TemporaryCustomObject() { ByteArrProp = new byte[] { 1, 2, 3 } }), false, false, "{\"type\":2,\"invocationId\":\"123\",\"item\":{\"StringProp\":\"SignalR!\",\"DoubleProp\":6.2831853071,\"IntProp\":42,\"DateTimeProp\":\"2017-04-11T00:00:00Z\",\"NullProp\":null,\"ByteArrProp\":\"AQID\"}}"), - new JsonProtocolTestData("StreamItemMessage_HasCustomItemWithNullValueInclude", new StreamItemMessage("123", new TemporaryCustomObject() { ByteArrProp = new byte[] { 1, 2, 3 } }), true, false, "{\"type\":2,\"invocationId\":\"123\",\"item\":{\"stringProp\":\"SignalR!\",\"doubleProp\":6.2831853071,\"intProp\":42,\"dateTimeProp\":\"2017-04-11T00:00:00Z\",\"nullProp\":null,\"byteArrProp\":\"AQID\"}}"), new JsonProtocolTestData("StreamItemMessage_HasFloatItem", new StreamItemMessage("123", 2.0f), true, true, "{\"type\":2,\"invocationId\":\"123\",\"item\":2}"), - new JsonProtocolTestData("StreamItemMessage_HasHeaders", AddHeaders(TestHeaders, new StreamItemMessage("123", new TemporaryCustomObject() { ByteArrProp = new byte[] { 1, 2, 3 } })), true, false, "{\"type\":2," + SerializedHeaders + ",\"invocationId\":\"123\",\"item\":{\"stringProp\":\"SignalR!\",\"doubleProp\":6.2831853071,\"intProp\":42,\"dateTimeProp\":\"2017-04-11T00:00:00Z\",\"nullProp\":null,\"byteArrProp\":\"AQID\"}}"), new JsonProtocolTestData("CompletionMessage_HasFloatResult", CompletionMessage.WithResult("123", 2.0f), true, true, "{\"type\":3,\"invocationId\":\"123\",\"result\":2}"), - new JsonProtocolTestData("CompletionMessage_HasCustomResultWithNoCamelCase", CompletionMessage.WithResult("123", new TemporaryCustomObject() { ByteArrProp = new byte[] { 1, 2, 3 } }), false, true, "{\"type\":3,\"invocationId\":\"123\",\"result\":{\"StringProp\":\"SignalR!\",\"DoubleProp\":6.2831853071,\"IntProp\":42,\"DateTimeProp\":\"2017-04-11T00:00:00Z\",\"ByteArrProp\":\"AQID\"}}"), - new JsonProtocolTestData("CompletionMessage_HasCustomResultWithNullValueIgnore", CompletionMessage.WithResult("123", new TemporaryCustomObject() { ByteArrProp = new byte[] { 1, 2, 3 } }), true, true, "{\"type\":3,\"invocationId\":\"123\",\"result\":{\"stringProp\":\"SignalR!\",\"doubleProp\":6.2831853071,\"intProp\":42,\"dateTimeProp\":\"2017-04-11T00:00:00Z\",\"byteArrProp\":\"AQID\"}}"), - new JsonProtocolTestData("CompletionMessage_HasCustomResultWithNullValueIncludeAndNoCamelCase", CompletionMessage.WithResult("123", new TemporaryCustomObject() { ByteArrProp = new byte[] { 1, 2, 3 } }), false, false, "{\"type\":3,\"invocationId\":\"123\",\"result\":{\"StringProp\":\"SignalR!\",\"DoubleProp\":6.2831853071,\"IntProp\":42,\"DateTimeProp\":\"2017-04-11T00:00:00Z\",\"NullProp\":null,\"ByteArrProp\":\"AQID\"}}"), - new JsonProtocolTestData("CompletionMessage_HasCustomResultWithNullValueInclude", CompletionMessage.WithResult("123", new TemporaryCustomObject() { ByteArrProp = new byte[] { 1, 2, 3 } }), true, false, "{\"type\":3,\"invocationId\":\"123\",\"result\":{\"stringProp\":\"SignalR!\",\"doubleProp\":6.2831853071,\"intProp\":42,\"dateTimeProp\":\"2017-04-11T00:00:00Z\",\"nullProp\":null,\"byteArrProp\":\"AQID\"}}"), - new JsonProtocolTestData("CompletionMessage_HasErrorAndCamelCase", CompletionMessage.Empty("123"), true, true, "{\"type\":3,\"invocationId\":\"123\"}"), - new JsonProtocolTestData("StreamInvocationMessage_HasCustomArgumentWithNoCamelCase", new StreamInvocationMessage("123", "Target", new object[] { new TemporaryCustomObject() { ByteArrProp = new byte[] { 1, 2, 3 } } }), false, true, "{\"type\":4,\"invocationId\":\"123\",\"target\":\"Target\",\"arguments\":[{\"StringProp\":\"SignalR!\",\"DoubleProp\":6.2831853071,\"IntProp\":42,\"DateTimeProp\":\"2017-04-11T00:00:00Z\",\"ByteArrProp\":\"AQID\"}]}"), - new JsonProtocolTestData("StreamInvocationMessage_HasCustomArgumentWithNullValueIgnore", new StreamInvocationMessage("123", "Target", new object[] { new TemporaryCustomObject() { ByteArrProp = new byte[] { 1, 2, 3 } } }), true, true, "{\"type\":4,\"invocationId\":\"123\",\"target\":\"Target\",\"arguments\":[{\"stringProp\":\"SignalR!\",\"doubleProp\":6.2831853071,\"intProp\":42,\"dateTimeProp\":\"2017-04-11T00:00:00Z\",\"byteArrProp\":\"AQID\"}]}"), - new JsonProtocolTestData("StreamInvocationMessage_HasCustomArgumentWithNullValueIgnoreAndNoCamelCase", new StreamInvocationMessage("123", "Target", new object[] { new TemporaryCustomObject() { ByteArrProp = new byte[] { 1, 2, 3 } } }), false, false, "{\"type\":4,\"invocationId\":\"123\",\"target\":\"Target\",\"arguments\":[{\"StringProp\":\"SignalR!\",\"DoubleProp\":6.2831853071,\"IntProp\":42,\"DateTimeProp\":\"2017-04-11T00:00:00Z\",\"NullProp\":null,\"ByteArrProp\":\"AQID\"}]}"), - new JsonProtocolTestData("StreamInvocationMessage_HasCustomArgumentWithNullValueInclude", new StreamInvocationMessage("123", "Target", new object[] { new TemporaryCustomObject() { ByteArrProp = new byte[] { 1, 2, 3 } } }), true, false, "{\"type\":4,\"invocationId\":\"123\",\"target\":\"Target\",\"arguments\":[{\"stringProp\":\"SignalR!\",\"doubleProp\":6.2831853071,\"intProp\":42,\"dateTimeProp\":\"2017-04-11T00:00:00Z\",\"nullProp\":null,\"byteArrProp\":\"AQID\"}]}"), new JsonProtocolTestData("StreamInvocationMessage_HasFloatArgument", new StreamInvocationMessage("123", "Target", new object[] { 1, "Foo", 2.0f }), true, true, "{\"type\":4,\"invocationId\":\"123\",\"target\":\"Target\",\"arguments\":[1,\"Foo\",2]}"), - new JsonProtocolTestData("StreamInvocationMessage_HasHeaders", AddHeaders(TestHeaders, new StreamInvocationMessage("123", "Target", new object[] { new TemporaryCustomObject() { ByteArrProp = new byte[] { 1, 2, 3 } } })), true, false, "{\"type\":4," + SerializedHeaders + ",\"invocationId\":\"123\",\"target\":\"Target\",\"arguments\":[{\"stringProp\":\"SignalR!\",\"doubleProp\":6.2831853071,\"intProp\":42,\"dateTimeProp\":\"2017-04-11T00:00:00Z\",\"nullProp\":null,\"byteArrProp\":\"AQID\"}]}"), }.ToDictionary(t => t.Name); public static IEnumerable CustomProtocolTestDataNames => CustomProtocolTestData.Keys.Select(name => new object[] { name }); } - - // Revert back to CustomObject when initial values on arrays are supported - // e.g. byte[] arr { get; set; } = byte[] { 1, 2, 3 }; - internal class TemporaryCustomObject : IEquatable - { - // Not intended to be a full set of things, just a smattering of sample serializations - public string StringProp { get; set; } = "SignalR!"; - - public double DoubleProp { get; set; } = 6.2831853071; - - public int IntProp { get; set; } = 42; - - public DateTime DateTimeProp { get; set; } = new DateTime(2017, 4, 11, 0, 0, 0, DateTimeKind.Utc); - - public object NullProp { get; set; } = null; - - public byte[] ByteArrProp { get; set; } - - public override bool Equals(object obj) - { - return obj is TemporaryCustomObject o && Equals(o); - } - - public override int GetHashCode() - { - // This is never used in a hash table - return 0; - } - - public bool Equals(TemporaryCustomObject right) - { - // This allows the comparer below to properly compare the object in the test. - return string.Equals(StringProp, right.StringProp, StringComparison.Ordinal) && - DoubleProp == right.DoubleProp && - IntProp == right.IntProp && - DateTime.Equals(DateTimeProp, right.DateTimeProp) && - NullProp == right.NullProp && - System.Linq.Enumerable.SequenceEqual(ByteArrProp, right.ByteArrProp); - } - } } diff --git a/src/SignalR/common/SignalR.Common/test/Internal/Protocol/JsonHubProtocolTestsBase.cs b/src/SignalR/common/SignalR.Common/test/Internal/Protocol/JsonHubProtocolTestsBase.cs index d0ef4a6e7f..1570b54462 100644 --- a/src/SignalR/common/SignalR.Common/test/Internal/Protocol/JsonHubProtocolTestsBase.cs +++ b/src/SignalR/common/SignalR.Common/test/Internal/Protocol/JsonHubProtocolTestsBase.cs @@ -40,12 +40,30 @@ namespace Microsoft.AspNetCore.SignalR.Common.Tests.Internal.Protocol new JsonProtocolTestData("InvocationMessage_HasStreamAndNormalArgument", new InvocationMessage(null, "Target", new object[] { 42 }, new string[] { "__test_id__" }), true, true, "{\"type\":1,\"target\":\"Target\",\"arguments\":[42],\"streamIds\":[\"__test_id__\"]}"), new JsonProtocolTestData("InvocationMessage_HasMultipleStreams", new InvocationMessage(null, "Target", Array.Empty(), new string[] { "__test_id__", "__test_id2__" }), true, true, "{\"type\":1,\"target\":\"Target\",\"arguments\":[],\"streamIds\":[\"__test_id__\",\"__test_id2__\"]}"), new JsonProtocolTestData("InvocationMessage_DateTimeOffsetArgument", new InvocationMessage("Method", new object[] { DateTimeOffset.Parse("2016-05-10T13:51:20+12:34") }), true, true, "{\"type\":1,\"target\":\"Method\",\"arguments\":[\"2016-05-10T13:51:20+12:34\"]}"), + new JsonProtocolTestData("InvocationMessage_StringIsoDateArgument", new InvocationMessage("Method", new object[] { "2016-05-10T13:51:20+12:34" }), true, true, "{\"type\":1,\"target\":\"Method\",\"arguments\":[\"2016-05-10T13:51:20+12:34\"]}"), + new JsonProtocolTestData("InvocationMessage_HasNonAsciiArgument", new InvocationMessage("Method", new object[] { "מחרוזת כלשהי" }), true, true, "{\"type\":1,\"target\":\"Method\",\"arguments\":[\"מחרוזת כלשהי\"]}"), + new JsonProtocolTestData("InvocationMessage_HasCustomArgumentWithNoCamelCase", new InvocationMessage(null, "Target", new object[] { new CustomObject() }), false, true, "{\"type\":1,\"target\":\"Target\",\"arguments\":[{\"StringProp\":\"SignalR!\",\"DoubleProp\":6.2831853071,\"IntProp\":42,\"DateTimeProp\":\"2017-04-11T00:00:00Z\",\"ByteArrProp\":\"AQID\"}]}"), + new JsonProtocolTestData("InvocationMessage_HasCustomArgumentWithNullValueIgnore", new InvocationMessage(null, "Target", new object[] { new CustomObject() }), true, true, "{\"type\":1,\"target\":\"Target\",\"arguments\":[{\"stringProp\":\"SignalR!\",\"doubleProp\":6.2831853071,\"intProp\":42,\"dateTimeProp\":\"2017-04-11T00:00:00Z\",\"byteArrProp\":\"AQID\"}]}"), + new JsonProtocolTestData("InvocationMessage_HasCustomArgumentWithNullValueIgnoreAndNoCamelCase", new InvocationMessage(null, "Target", new object[] { new CustomObject() }), false, false, "{\"type\":1,\"target\":\"Target\",\"arguments\":[{\"StringProp\":\"SignalR!\",\"DoubleProp\":6.2831853071,\"IntProp\":42,\"DateTimeProp\":\"2017-04-11T00:00:00Z\",\"NullProp\":null,\"ByteArrProp\":\"AQID\"}]}"), + new JsonProtocolTestData("InvocationMessage_HasCustomArgumentWithNullValueInclude", new InvocationMessage(null, "Target", new object[] { new CustomObject() }), true, false, "{\"type\":1,\"target\":\"Target\",\"arguments\":[{\"stringProp\":\"SignalR!\",\"doubleProp\":6.2831853071,\"intProp\":42,\"dateTimeProp\":\"2017-04-11T00:00:00Z\",\"nullProp\":null,\"byteArrProp\":\"AQID\"}]}"), + new JsonProtocolTestData("StreamItemMessage_HasHeaders", AddHeaders(TestHeaders, new StreamItemMessage("123", new CustomObject())), true, false, "{\"type\":2," + SerializedHeaders + ",\"invocationId\":\"123\",\"item\":{\"stringProp\":\"SignalR!\",\"doubleProp\":6.2831853071,\"intProp\":42,\"dateTimeProp\":\"2017-04-11T00:00:00Z\",\"nullProp\":null,\"byteArrProp\":\"AQID\"}}"), + new JsonProtocolTestData("StreamItemMessage_HasCustomItemWithNoCamelCase", new StreamItemMessage("123", new CustomObject()), false, true, "{\"type\":2,\"invocationId\":\"123\",\"item\":{\"StringProp\":\"SignalR!\",\"DoubleProp\":6.2831853071,\"IntProp\":42,\"DateTimeProp\":\"2017-04-11T00:00:00Z\",\"ByteArrProp\":\"AQID\"}}"), + new JsonProtocolTestData("StreamItemMessage_HasCustomItemWithNullValueIgnore", new StreamItemMessage("123", new CustomObject()), true, true, "{\"type\":2,\"invocationId\":\"123\",\"item\":{\"stringProp\":\"SignalR!\",\"doubleProp\":6.2831853071,\"intProp\":42,\"dateTimeProp\":\"2017-04-11T00:00:00Z\",\"byteArrProp\":\"AQID\"}}"), + new JsonProtocolTestData("StreamItemMessage_HasCustomItemWithNullValueIgnoreAndNoCamelCase", new StreamItemMessage("123", new CustomObject()), false, false, "{\"type\":2,\"invocationId\":\"123\",\"item\":{\"StringProp\":\"SignalR!\",\"DoubleProp\":6.2831853071,\"IntProp\":42,\"DateTimeProp\":\"2017-04-11T00:00:00Z\",\"NullProp\":null,\"ByteArrProp\":\"AQID\"}}"), + new JsonProtocolTestData("StreamItemMessage_HasCustomItemWithNullValueInclude", new StreamItemMessage("123", new CustomObject()), true, false, "{\"type\":2,\"invocationId\":\"123\",\"item\":{\"stringProp\":\"SignalR!\",\"doubleProp\":6.2831853071,\"intProp\":42,\"dateTimeProp\":\"2017-04-11T00:00:00Z\",\"nullProp\":null,\"byteArrProp\":\"AQID\"}}"), new JsonProtocolTestData("StreamItemMessage_HasIntegerItem", new StreamItemMessage("123", 1), true, true, "{\"type\":2,\"invocationId\":\"123\",\"item\":1}"), new JsonProtocolTestData("StreamItemMessage_HasStringItem", new StreamItemMessage("123", "Foo"), true, true, "{\"type\":2,\"invocationId\":\"123\",\"item\":\"Foo\"}"), new JsonProtocolTestData("StreamItemMessage_HasBoolItem", new StreamItemMessage("123", true), true, true, "{\"type\":2,\"invocationId\":\"123\",\"item\":true}"), new JsonProtocolTestData("StreamItemMessage_HasNullItem", new StreamItemMessage("123", null), true, true, "{\"type\":2,\"invocationId\":\"123\",\"item\":null}"), + new JsonProtocolTestData("CompletionMessage_HasCustomResultWithNoCamelCase", CompletionMessage.WithResult("123", new CustomObject()), false, true, "{\"type\":3,\"invocationId\":\"123\",\"result\":{\"StringProp\":\"SignalR!\",\"DoubleProp\":6.2831853071,\"IntProp\":42,\"DateTimeProp\":\"2017-04-11T00:00:00Z\",\"ByteArrProp\":\"AQID\"}}"), + new JsonProtocolTestData("CompletionMessage_HasCustomResultWithNullValueIgnore", CompletionMessage.WithResult("123", new CustomObject()), true, true, "{\"type\":3,\"invocationId\":\"123\",\"result\":{\"stringProp\":\"SignalR!\",\"doubleProp\":6.2831853071,\"intProp\":42,\"dateTimeProp\":\"2017-04-11T00:00:00Z\",\"byteArrProp\":\"AQID\"}}"), + new JsonProtocolTestData("CompletionMessage_HasCustomResultWithNullValueIncludeAndNoCamelCase", CompletionMessage.WithResult("123", new CustomObject()), false, false, "{\"type\":3,\"invocationId\":\"123\",\"result\":{\"StringProp\":\"SignalR!\",\"DoubleProp\":6.2831853071,\"IntProp\":42,\"DateTimeProp\":\"2017-04-11T00:00:00Z\",\"NullProp\":null,\"ByteArrProp\":\"AQID\"}}"), + new JsonProtocolTestData("CompletionMessage_HasCustomResultWithNullValueInclude", CompletionMessage.WithResult("123", new CustomObject()), true, false, "{\"type\":3,\"invocationId\":\"123\",\"result\":{\"stringProp\":\"SignalR!\",\"doubleProp\":6.2831853071,\"intProp\":42,\"dateTimeProp\":\"2017-04-11T00:00:00Z\",\"nullProp\":null,\"byteArrProp\":\"AQID\"}}"), + new JsonProtocolTestData("CompletionMessage_HasErrorAndCamelCase", CompletionMessage.Empty("123"), true, true, "{\"type\":3,\"invocationId\":\"123\"}"), + new JsonProtocolTestData("CompletionMessage_HasTestHeadersAndCustomItemResult", AddHeaders(TestHeaders, CompletionMessage.WithResult("123", new CustomObject())), true, false, "{\"type\":3," + SerializedHeaders + ",\"invocationId\":\"123\",\"result\":{\"stringProp\":\"SignalR!\",\"doubleProp\":6.2831853071,\"intProp\":42,\"dateTimeProp\":\"2017-04-11T00:00:00Z\",\"nullProp\":null,\"byteArrProp\":\"AQID\"}}"), + new JsonProtocolTestData("CompletionMessage_HasErrorAndHeadersAndCamelCase", AddHeaders(TestHeaders, CompletionMessage.Empty("123")), true, true, "{\"type\":3," + SerializedHeaders + ",\"invocationId\":\"123\"}"), new JsonProtocolTestData("CompletionMessage_HasIntegerResult", CompletionMessage.WithResult("123", 1), true, true, "{\"type\":3,\"invocationId\":\"123\",\"result\":1}"), new JsonProtocolTestData("CompletionMessage_HasStringResult", CompletionMessage.WithResult("123", "Foo"), true, true, "{\"type\":3,\"invocationId\":\"123\",\"result\":\"Foo\"}"), new JsonProtocolTestData("CompletionMessage_HasBoolResult", CompletionMessage.WithResult("123", true), true, true, "{\"type\":3,\"invocationId\":\"123\",\"result\":true}"), @@ -53,6 +71,11 @@ namespace Microsoft.AspNetCore.SignalR.Common.Tests.Internal.Protocol new JsonProtocolTestData("CompletionMessage_HasError", CompletionMessage.WithError("123", "Whoops!"), true, true, "{\"type\":3,\"invocationId\":\"123\",\"error\":\"Whoops!\"}"), new JsonProtocolTestData("CompletionMessage_HasErrorAndHeaders", AddHeaders(TestHeaders, CompletionMessage.WithError("123", "Whoops!")), true, true, "{\"type\":3," + SerializedHeaders + ",\"invocationId\":\"123\",\"error\":\"Whoops!\"}"), + new JsonProtocolTestData("StreamInvocationMessage_HasHeaders", AddHeaders(TestHeaders, new StreamInvocationMessage("123", "Target", new object[] { new CustomObject() })), true, false, "{\"type\":4," + SerializedHeaders + ",\"invocationId\":\"123\",\"target\":\"Target\",\"arguments\":[{\"stringProp\":\"SignalR!\",\"doubleProp\":6.2831853071,\"intProp\":42,\"dateTimeProp\":\"2017-04-11T00:00:00Z\",\"nullProp\":null,\"byteArrProp\":\"AQID\"}]}"), + new JsonProtocolTestData("StreamInvocationMessage_HasCustomArgumentWithNoCamelCase", new StreamInvocationMessage("123", "Target", new object[] { new CustomObject() }), false, true, "{\"type\":4,\"invocationId\":\"123\",\"target\":\"Target\",\"arguments\":[{\"StringProp\":\"SignalR!\",\"DoubleProp\":6.2831853071,\"IntProp\":42,\"DateTimeProp\":\"2017-04-11T00:00:00Z\",\"ByteArrProp\":\"AQID\"}]}"), + new JsonProtocolTestData("StreamInvocationMessage_HasCustomArgumentWithNullValueIgnore", new StreamInvocationMessage("123", "Target", new object[] { new CustomObject() }), true, true, "{\"type\":4,\"invocationId\":\"123\",\"target\":\"Target\",\"arguments\":[{\"stringProp\":\"SignalR!\",\"doubleProp\":6.2831853071,\"intProp\":42,\"dateTimeProp\":\"2017-04-11T00:00:00Z\",\"byteArrProp\":\"AQID\"}]}"), + new JsonProtocolTestData("StreamInvocationMessage_HasCustomArgumentWithNullValueIgnoreAndNoCamelCase", new StreamInvocationMessage("123", "Target", new object[] { new CustomObject() }), false, false, "{\"type\":4,\"invocationId\":\"123\",\"target\":\"Target\",\"arguments\":[{\"StringProp\":\"SignalR!\",\"DoubleProp\":6.2831853071,\"IntProp\":42,\"DateTimeProp\":\"2017-04-11T00:00:00Z\",\"NullProp\":null,\"ByteArrProp\":\"AQID\"}]}"), + new JsonProtocolTestData("StreamInvocationMessage_HasCustomArgumentWithNullValueInclude", new StreamInvocationMessage("123", "Target", new object[] { new CustomObject() }), true, false, "{\"type\":4,\"invocationId\":\"123\",\"target\":\"Target\",\"arguments\":[{\"stringProp\":\"SignalR!\",\"doubleProp\":6.2831853071,\"intProp\":42,\"dateTimeProp\":\"2017-04-11T00:00:00Z\",\"nullProp\":null,\"byteArrProp\":\"AQID\"}]}"), new JsonProtocolTestData("StreamInvocationMessage_HasInvocationId", new StreamInvocationMessage("123", "Target", new object[] { 1, "Foo" }), true, true, "{\"type\":4,\"invocationId\":\"123\",\"target\":\"Target\",\"arguments\":[1,\"Foo\"]}"), new JsonProtocolTestData("StreamInvocationMessage_HasBoolArgument", new StreamInvocationMessage("123", "Target", new object[] { true }), true, true, "{\"type\":4,\"invocationId\":\"123\",\"target\":\"Target\",\"arguments\":[true]}"), new JsonProtocolTestData("StreamInvocationMessage_HasNullArgument", new StreamInvocationMessage("123", "Target", new object[] { null }), true, true, "{\"type\":4,\"invocationId\":\"123\",\"target\":\"Target\",\"arguments\":[null]}"), @@ -156,8 +179,7 @@ namespace Microsoft.AspNetCore.SignalR.Common.Tests.Internal.Protocol [InlineData("{\"type\":4,\"invocationId\":\"42\",\"target\":\"foo\"}", "Missing required property 'arguments'.")] [InlineData("{\"type\":4,\"invocationId\":\"42\",\"target\":\"foo\",\"arguments\":{}}", "Expected 'arguments' to be of type Array.")] - //[InlineData("{\"type\":3,\"invocationId\":\"42\",\"error\":\"foo\",\"result\":true}", "The 'error' and 'result' properties are mutually exclusive.")] - //[InlineData("{\"type\":3,\"invocationId\":\"42\",\"result\":true", "Unexpected end when reading JSON.")] + [InlineData("{\"type\":3,\"invocationId\":\"42\",\"error\":\"foo\",\"result\":true}", "The 'error' and 'result' properties are mutually exclusive.")] public void InvalidMessages(string input, string expectedMessage) { input = Frame(input); @@ -270,6 +292,26 @@ namespace Microsoft.AspNetCore.SignalR.Common.Tests.Internal.Protocol Assert.Equal("foo", bindingFailure.Target); } + [Fact] + public void ReadCaseInsensitivePropertiesByDefault() + { + var input = Frame("{\"type\":2,\"invocationId\":\"123\",\"item\":{\"StrIngProp\":\"test\",\"DoublePrOp\":3.14159,\"IntProp\":43,\"DateTimeProp\":\"2019-06-03T22:00:00\",\"NuLLProp\":null,\"ByteARRProp\":\"AgQG\"}}"); + + var binder = new TestBinder(null, typeof(CustomObject)); + var data = new ReadOnlySequence(Encoding.UTF8.GetBytes(input)); + JsonHubProtocol.TryParseMessage(ref data, binder, out var message); + + var streamItemMessage = Assert.IsType(message); + Assert.Equal(new CustomObject() + { + ByteArrProp = new byte[] { 2, 4, 6 }, + IntProp = 43, + DoubleProp = 3.14159, + StringProp = "test", + DateTimeProp = DateTime.Parse("6/3/2019 10:00:00 PM") + }, streamItemMessage.Item); + } + public static string Frame(string input) { var data = Encoding.UTF8.GetBytes(input); diff --git a/src/SignalR/common/SignalR.Common/test/Internal/Protocol/NewtonsoftJsonHubProtocolTests.cs b/src/SignalR/common/SignalR.Common/test/Internal/Protocol/NewtonsoftJsonHubProtocolTests.cs index 255cdead83..de10b1dbf6 100644 --- a/src/SignalR/common/SignalR.Common/test/Internal/Protocol/NewtonsoftJsonHubProtocolTests.cs +++ b/src/SignalR/common/SignalR.Common/test/Internal/Protocol/NewtonsoftJsonHubProtocolTests.cs @@ -40,6 +40,7 @@ namespace Microsoft.AspNetCore.SignalR.Common.Tests.Internal.Protocol [InlineData("", "Unexpected end when reading JSON.")] [InlineData("42", "Unexpected JSON Token Type 'Integer'. Expected a JSON Object.")] [InlineData("{\"type\":\"foo\"}", "Expected 'type' to be of type Integer.")] + [InlineData("{\"type\":3,\"invocationId\":\"42\",\"result\":true", "Unexpected end when reading JSON.")] public void CustomInvalidMessages(string input, string expectedMessage) { input = Frame(input); @@ -93,35 +94,13 @@ namespace Microsoft.AspNetCore.SignalR.Common.Tests.Internal.Protocol public static IDictionary CustomProtocolTestData => new[] { new JsonProtocolTestData("InvocationMessage_HasFloatArgument", new InvocationMessage(null, "Target", new object[] { 1, "Foo", 2.0f }), true, true, "{\"type\":1,\"target\":\"Target\",\"arguments\":[1,\"Foo\",2.0]}"), - new JsonProtocolTestData("InvocationMessage_StringIsoDateArgument", new InvocationMessage("Method", new object[] { "2016-05-10T13:51:20+12:34" }), false, true, "{\"type\":1,\"target\":\"Method\",\"arguments\":[\"2016-05-10T13:51:20+12:34\"]}"), - new JsonProtocolTestData("InvocationMessage_HasCustomArgumentWithNoCamelCase", new InvocationMessage(null, "Target", new object[] { new CustomObject() }), false, true, "{\"type\":1,\"target\":\"Target\",\"arguments\":[{\"StringProp\":\"SignalR!\",\"DoubleProp\":6.2831853071,\"IntProp\":42,\"DateTimeProp\":\"2017-04-11T00:00:00Z\",\"ByteArrProp\":\"AQID\"}]}"), - new JsonProtocolTestData("InvocationMessage_HasCustomArgumentWithNullValueIgnore", new InvocationMessage(null, "Target", new object[] { new CustomObject() }), true, true, "{\"type\":1,\"target\":\"Target\",\"arguments\":[{\"stringProp\":\"SignalR!\",\"doubleProp\":6.2831853071,\"intProp\":42,\"dateTimeProp\":\"2017-04-11T00:00:00Z\",\"byteArrProp\":\"AQID\"}]}"), - new JsonProtocolTestData("InvocationMessage_HasCustomArgumentWithNullValueIgnoreAndNoCamelCase", new InvocationMessage(null, "Target", new object[] { new CustomObject() }), false, false, "{\"type\":1,\"target\":\"Target\",\"arguments\":[{\"StringProp\":\"SignalR!\",\"DoubleProp\":6.2831853071,\"IntProp\":42,\"DateTimeProp\":\"2017-04-11T00:00:00Z\",\"NullProp\":null,\"ByteArrProp\":\"AQID\"}]}"), - new JsonProtocolTestData("InvocationMessage_HasCustomArgumentWithNullValueInclude", new InvocationMessage(null, "Target", new object[] { new CustomObject() }), true, false, "{\"type\":1,\"target\":\"Target\",\"arguments\":[{\"stringProp\":\"SignalR!\",\"doubleProp\":6.2831853071,\"intProp\":42,\"dateTimeProp\":\"2017-04-11T00:00:00Z\",\"nullProp\":null,\"byteArrProp\":\"AQID\"}]}"), new JsonProtocolTestData("InvocationMessage_HasHeaders", AddHeaders(TestHeaders, new InvocationMessage("123", "Target", new object[] { 1, "Foo", 2.0f })), true, true, "{\"type\":1," + SerializedHeaders + ",\"invocationId\":\"123\",\"target\":\"Target\",\"arguments\":[1,\"Foo\",2.0]}"), new JsonProtocolTestData("StreamItemMessage_HasFloatItem", new StreamItemMessage("123", 2.0f), true, true, "{\"type\":2,\"invocationId\":\"123\",\"item\":2.0}"), - new JsonProtocolTestData("StreamItemMessage_HasCustomItemWithNoCamelCase", new StreamItemMessage("123", new CustomObject()), false, true, "{\"type\":2,\"invocationId\":\"123\",\"item\":{\"StringProp\":\"SignalR!\",\"DoubleProp\":6.2831853071,\"IntProp\":42,\"DateTimeProp\":\"2017-04-11T00:00:00Z\",\"ByteArrProp\":\"AQID\"}}"), - new JsonProtocolTestData("StreamItemMessage_HasCustomItemWithNullValueIgnore", new StreamItemMessage("123", new CustomObject()), true, true, "{\"type\":2,\"invocationId\":\"123\",\"item\":{\"stringProp\":\"SignalR!\",\"doubleProp\":6.2831853071,\"intProp\":42,\"dateTimeProp\":\"2017-04-11T00:00:00Z\",\"byteArrProp\":\"AQID\"}}"), - new JsonProtocolTestData("StreamItemMessage_HasCustomItemWithNullValueIgnoreAndNoCamelCase", new StreamItemMessage("123", new CustomObject()), false, false, "{\"type\":2,\"invocationId\":\"123\",\"item\":{\"StringProp\":\"SignalR!\",\"DoubleProp\":6.2831853071,\"IntProp\":42,\"DateTimeProp\":\"2017-04-11T00:00:00Z\",\"NullProp\":null,\"ByteArrProp\":\"AQID\"}}"), - new JsonProtocolTestData("StreamItemMessage_HasCustomItemWithNullValueInclude", new StreamItemMessage("123", new CustomObject()), true, false, "{\"type\":2,\"invocationId\":\"123\",\"item\":{\"stringProp\":\"SignalR!\",\"doubleProp\":6.2831853071,\"intProp\":42,\"dateTimeProp\":\"2017-04-11T00:00:00Z\",\"nullProp\":null,\"byteArrProp\":\"AQID\"}}"), - new JsonProtocolTestData("StreamItemMessage_HasHeaders", AddHeaders(TestHeaders, new StreamItemMessage("123", new CustomObject())), true, false, "{\"type\":2," + SerializedHeaders + ",\"invocationId\":\"123\",\"item\":{\"stringProp\":\"SignalR!\",\"doubleProp\":6.2831853071,\"intProp\":42,\"dateTimeProp\":\"2017-04-11T00:00:00Z\",\"nullProp\":null,\"byteArrProp\":\"AQID\"}}"), - new JsonProtocolTestData("CompletionMessage_HasCustomResultWithNoCamelCase", CompletionMessage.WithResult("123", new CustomObject()), false, true, "{\"type\":3,\"invocationId\":\"123\",\"result\":{\"StringProp\":\"SignalR!\",\"DoubleProp\":6.2831853071,\"IntProp\":42,\"DateTimeProp\":\"2017-04-11T00:00:00Z\",\"ByteArrProp\":\"AQID\"}}"), - new JsonProtocolTestData("CompletionMessage_HasCustomResultWithNullValueIgnore", CompletionMessage.WithResult("123", new CustomObject()), true, true, "{\"type\":3,\"invocationId\":\"123\",\"result\":{\"stringProp\":\"SignalR!\",\"doubleProp\":6.2831853071,\"intProp\":42,\"dateTimeProp\":\"2017-04-11T00:00:00Z\",\"byteArrProp\":\"AQID\"}}"), - new JsonProtocolTestData("CompletionMessage_HasCustomResultWithNullValueIncludeAndNoCamelCase", CompletionMessage.WithResult("123", new CustomObject()), false, false, "{\"type\":3,\"invocationId\":\"123\",\"result\":{\"StringProp\":\"SignalR!\",\"DoubleProp\":6.2831853071,\"IntProp\":42,\"DateTimeProp\":\"2017-04-11T00:00:00Z\",\"NullProp\":null,\"ByteArrProp\":\"AQID\"}}"), - new JsonProtocolTestData("CompletionMessage_HasCustomResultWithNullValueInclude", CompletionMessage.WithResult("123", new CustomObject()), true, false, "{\"type\":3,\"invocationId\":\"123\",\"result\":{\"stringProp\":\"SignalR!\",\"doubleProp\":6.2831853071,\"intProp\":42,\"dateTimeProp\":\"2017-04-11T00:00:00Z\",\"nullProp\":null,\"byteArrProp\":\"AQID\"}}"), - new JsonProtocolTestData("CompletionMessage_HasTestHeadersAndCustomItemResult", AddHeaders(TestHeaders, CompletionMessage.WithResult("123", new CustomObject())), true, false, "{\"type\":3," + SerializedHeaders + ",\"invocationId\":\"123\",\"result\":{\"stringProp\":\"SignalR!\",\"doubleProp\":6.2831853071,\"intProp\":42,\"dateTimeProp\":\"2017-04-11T00:00:00Z\",\"nullProp\":null,\"byteArrProp\":\"AQID\"}}"), - new JsonProtocolTestData("CompletionMessage_HasErrorAndCamelCase", CompletionMessage.Empty("123"), true, true, "{\"type\":3,\"invocationId\":\"123\"}"), - new JsonProtocolTestData("CompletionMessage_HasErrorAndHeadersAndCamelCase", AddHeaders(TestHeaders, CompletionMessage.Empty("123")), true, true, "{\"type\":3," + SerializedHeaders + ",\"invocationId\":\"123\"}"), new JsonProtocolTestData("CompletionMessage_HasFloatResult", CompletionMessage.WithResult("123", 2.0f), true, true, "{\"type\":3,\"invocationId\":\"123\",\"result\":2.0}"), new JsonProtocolTestData("StreamInvocationMessage_HasFloatArgument", new StreamInvocationMessage("123", "Target", new object[] { 1, "Foo", 2.0f }), true, true, "{\"type\":4,\"invocationId\":\"123\",\"target\":\"Target\",\"arguments\":[1,\"Foo\",2.0]}"), - new JsonProtocolTestData("StreamInvocationMessage_HasCustomArgumentWithNoCamelCase", new StreamInvocationMessage("123", "Target", new object[] { new CustomObject() }), false, true, "{\"type\":4,\"invocationId\":\"123\",\"target\":\"Target\",\"arguments\":[{\"StringProp\":\"SignalR!\",\"DoubleProp\":6.2831853071,\"IntProp\":42,\"DateTimeProp\":\"2017-04-11T00:00:00Z\",\"ByteArrProp\":\"AQID\"}]}"), - new JsonProtocolTestData("StreamInvocationMessage_HasCustomArgumentWithNullValueIgnore", new StreamInvocationMessage("123", "Target", new object[] { new CustomObject() }), true, true, "{\"type\":4,\"invocationId\":\"123\",\"target\":\"Target\",\"arguments\":[{\"stringProp\":\"SignalR!\",\"doubleProp\":6.2831853071,\"intProp\":42,\"dateTimeProp\":\"2017-04-11T00:00:00Z\",\"byteArrProp\":\"AQID\"}]}"), - new JsonProtocolTestData("StreamInvocationMessage_HasCustomArgumentWithNullValueIgnoreAndNoCamelCase", new StreamInvocationMessage("123", "Target", new object[] { new CustomObject() }), false, false, "{\"type\":4,\"invocationId\":\"123\",\"target\":\"Target\",\"arguments\":[{\"StringProp\":\"SignalR!\",\"DoubleProp\":6.2831853071,\"IntProp\":42,\"DateTimeProp\":\"2017-04-11T00:00:00Z\",\"NullProp\":null,\"ByteArrProp\":\"AQID\"}]}"), - new JsonProtocolTestData("StreamInvocationMessage_HasCustomArgumentWithNullValueInclude", new StreamInvocationMessage("123", "Target", new object[] { new CustomObject() }), true, false, "{\"type\":4,\"invocationId\":\"123\",\"target\":\"Target\",\"arguments\":[{\"stringProp\":\"SignalR!\",\"doubleProp\":6.2831853071,\"intProp\":42,\"dateTimeProp\":\"2017-04-11T00:00:00Z\",\"nullProp\":null,\"byteArrProp\":\"AQID\"}]}"), - new JsonProtocolTestData("StreamInvocationMessage_HasHeaders", AddHeaders(TestHeaders, new StreamInvocationMessage("123", "Target", new object[] { new CustomObject() })), true, false, "{\"type\":4," + SerializedHeaders + ",\"invocationId\":\"123\",\"target\":\"Target\",\"arguments\":[{\"stringProp\":\"SignalR!\",\"doubleProp\":6.2831853071,\"intProp\":42,\"dateTimeProp\":\"2017-04-11T00:00:00Z\",\"nullProp\":null,\"byteArrProp\":\"AQID\"}]}"), }.ToDictionary(t => t.Name); public static IEnumerable CustomProtocolTestDataNames => CustomProtocolTestData.Keys.Select(name => new object[] { name }); diff --git a/src/SignalR/common/SignalR.Common/test/Internal/Protocol/Utf8BufferTextWriterTests.cs b/src/SignalR/common/SignalR.Common/test/Internal/Protocol/Utf8BufferTextWriterTests.cs index b1fe8c76fb..6b7a5563d3 100644 --- a/src/SignalR/common/SignalR.Common/test/Internal/Protocol/Utf8BufferTextWriterTests.cs +++ b/src/SignalR/common/SignalR.Common/test/Internal/Protocol/Utf8BufferTextWriterTests.cs @@ -1,4 +1,4 @@ -// Copyright (c) .NET Foundation. All rights reserved. +// 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; @@ -245,7 +245,7 @@ namespace Microsoft.AspNetCore.SignalR.Common.Tests.Internal.Protocol } [Fact] - private void WriteMultiByteCharactersToSmallBuffers() + public void WriteMultiByteCharactersToSmallBuffers() { // Test string breakdown (char => UTF-8 hex values): // a => 61 From ef83e3359d0a40f78c0e3e65d884db64c76cb5cb Mon Sep 17 00:00:00 2001 From: Pranav K Date: Thu, 15 Aug 2019 17:14:03 -0700 Subject: [PATCH 0164/1101] Change JSInterop to avoid using async locals (dotnet/extensions#2163) * Remove the use of async local JSRuntime * Update DotNetDispatcher to accept a JSRuntime instance rather than use a ambient value. * Modify DotNetObjectReference to start tracking it's value during serialization. \n\nCommit migrated from https://github.com/dotnet/extensions/commit/ae9878bb9945423ad20f0ba97033fcebfb5d8419 --- .../src/src/Microsoft.JSInterop.ts | 6 +- .../ref/Microsoft.JSInterop.netcoreapp3.0.cs | 8 +- .../ref/Microsoft.JSInterop.netstandard2.0.cs | 8 +- .../src/DotNetObjectReference.cs | 4 +- .../src/DotNetObjectReferenceOfT.cs | 42 ++- .../src/Infrastructure/DotNetDispatcher.cs | 49 ++-- .../DotNetObjectReferenceJsonConverter.cs | 15 +- ...tNetObjectReferenceJsonConverterFactory.cs | 9 +- .../DotNetObjectReferenceManager.cs | 51 ---- .../src/JSInProcessRuntime.cs | 4 +- .../Microsoft.JSInterop/src/JSRuntime.cs | 92 +++++-- .../src/JsonSerializerOptionsProvider.cs | 17 -- .../test/DotNetObjectReferenceTest.cs | 84 +++++- .../Infrastructure/DotNetDispatcherTest.cs | 255 +++++++++--------- .../DotNetObjectReferenceJsonConverterTest.cs | 72 ++--- .../test/JSInProcessRuntimeTest.cs | 9 +- .../Microsoft.JSInterop/test/JSRuntimeTest.cs | 30 +-- .../Microsoft.JSInterop/test/TestJSRuntime.cs | 12 - ...Mono.WebAssembly.Interop.netstandard2.0.cs | 1 + .../src/MonoWebAssemblyJSRuntime.cs | 27 +- 20 files changed, 432 insertions(+), 363 deletions(-) delete mode 100644 src/JSInterop/Microsoft.JSInterop/src/Infrastructure/DotNetObjectReferenceManager.cs delete mode 100644 src/JSInterop/Microsoft.JSInterop/src/JsonSerializerOptionsProvider.cs diff --git a/src/JSInterop/Microsoft.JSInterop.JS/src/src/Microsoft.JSInterop.ts b/src/JSInterop/Microsoft.JSInterop.JS/src/src/Microsoft.JSInterop.ts index 30a91bde4d..3af355f6d0 100644 --- a/src/JSInterop/Microsoft.JSInterop.JS/src/src/Microsoft.JSInterop.ts +++ b/src/JSInterop/Microsoft.JSInterop.JS/src/src/Microsoft.JSInterop.ts @@ -55,7 +55,7 @@ module DotNet { return invokePossibleInstanceMethodAsync(assemblyName, methodIdentifier, null, args); } - function invokePossibleInstanceMethod(assemblyName: string | null, methodIdentifier: string, dotNetObjectId: number | null, args: any[]): T { + function invokePossibleInstanceMethod(assemblyName: string | null, methodIdentifier: string, dotNetObjectId: number | null, args: any[] | null): T { const dispatcher = getRequiredDispatcher(); if (dispatcher.invokeDotNetFromJS) { const argsJson = JSON.stringify(args, argReplacer); @@ -66,7 +66,7 @@ module DotNet { } } - function invokePossibleInstanceMethodAsync(assemblyName: string | null, methodIdentifier: string, dotNetObjectId: number | null, ...args: any[]): Promise { + function invokePossibleInstanceMethodAsync(assemblyName: string | null, methodIdentifier: string, dotNetObjectId: number | null, args: any[] | null): Promise { if (assemblyName && dotNetObjectId) { throw new Error(`For instance method calls, assemblyName should be null. Received '${assemblyName}'.`) ; } @@ -273,7 +273,7 @@ module DotNet { } public dispose() { - const promise = invokePossibleInstanceMethodAsync(null, '__Dispose', this._id); + const promise = invokePossibleInstanceMethodAsync(null, '__Dispose', this._id, null); promise.catch(error => console.error(error)); } diff --git a/src/JSInterop/Microsoft.JSInterop/ref/Microsoft.JSInterop.netcoreapp3.0.cs b/src/JSInterop/Microsoft.JSInterop/ref/Microsoft.JSInterop.netcoreapp3.0.cs index a5fbbc768a..953f8b0329 100644 --- a/src/JSInterop/Microsoft.JSInterop/ref/Microsoft.JSInterop.netcoreapp3.0.cs +++ b/src/JSInterop/Microsoft.JSInterop/ref/Microsoft.JSInterop.netcoreapp3.0.cs @@ -48,11 +48,11 @@ namespace Microsoft.JSInterop { protected JSRuntime() { } protected System.TimeSpan? DefaultAsyncTimeout { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } + protected internal System.Text.Json.JsonSerializerOptions JsonSerializerOptions { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } } protected abstract void BeginInvokeJS(long taskId, string identifier, string argsJson); protected internal abstract void EndInvokeDotNet(string callId, bool success, object resultOrError, string assemblyName, string methodIdentifier, long dotNetObjectId); public System.Threading.Tasks.ValueTask InvokeAsync(string identifier, object[] args) { throw null; } public System.Threading.Tasks.ValueTask InvokeAsync(string identifier, System.Threading.CancellationToken cancellationToken, object[] args) { throw null; } - public static void SetCurrentJSRuntime(Microsoft.JSInterop.IJSRuntime instance) { } } public static partial class JSRuntimeExtensions { @@ -72,8 +72,8 @@ namespace Microsoft.JSInterop.Infrastructure { public static partial class DotNetDispatcher { - public static void BeginInvokeDotNet(string callId, string assemblyName, string methodIdentifier, long dotNetObjectId, string argsJson) { } - public static void EndInvokeJS(string arguments) { } - public static string Invoke(string assemblyName, string methodIdentifier, long dotNetObjectId, string argsJson) { throw null; } + public static void BeginInvokeDotNet(Microsoft.JSInterop.JSRuntime jsRuntime, string callId, string assemblyName, string methodIdentifier, long dotNetObjectId, string argsJson) { } + public static void EndInvokeJS(Microsoft.JSInterop.JSRuntime jsRuntime, string arguments) { } + public static string Invoke(Microsoft.JSInterop.JSRuntime jsRuntime, string assemblyName, string methodIdentifier, long dotNetObjectId, string argsJson) { throw null; } } } diff --git a/src/JSInterop/Microsoft.JSInterop/ref/Microsoft.JSInterop.netstandard2.0.cs b/src/JSInterop/Microsoft.JSInterop/ref/Microsoft.JSInterop.netstandard2.0.cs index a5fbbc768a..953f8b0329 100644 --- a/src/JSInterop/Microsoft.JSInterop/ref/Microsoft.JSInterop.netstandard2.0.cs +++ b/src/JSInterop/Microsoft.JSInterop/ref/Microsoft.JSInterop.netstandard2.0.cs @@ -48,11 +48,11 @@ namespace Microsoft.JSInterop { protected JSRuntime() { } protected System.TimeSpan? DefaultAsyncTimeout { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } + protected internal System.Text.Json.JsonSerializerOptions JsonSerializerOptions { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } } protected abstract void BeginInvokeJS(long taskId, string identifier, string argsJson); protected internal abstract void EndInvokeDotNet(string callId, bool success, object resultOrError, string assemblyName, string methodIdentifier, long dotNetObjectId); public System.Threading.Tasks.ValueTask InvokeAsync(string identifier, object[] args) { throw null; } public System.Threading.Tasks.ValueTask InvokeAsync(string identifier, System.Threading.CancellationToken cancellationToken, object[] args) { throw null; } - public static void SetCurrentJSRuntime(Microsoft.JSInterop.IJSRuntime instance) { } } public static partial class JSRuntimeExtensions { @@ -72,8 +72,8 @@ namespace Microsoft.JSInterop.Infrastructure { public static partial class DotNetDispatcher { - public static void BeginInvokeDotNet(string callId, string assemblyName, string methodIdentifier, long dotNetObjectId, string argsJson) { } - public static void EndInvokeJS(string arguments) { } - public static string Invoke(string assemblyName, string methodIdentifier, long dotNetObjectId, string argsJson) { throw null; } + public static void BeginInvokeDotNet(Microsoft.JSInterop.JSRuntime jsRuntime, string callId, string assemblyName, string methodIdentifier, long dotNetObjectId, string argsJson) { } + public static void EndInvokeJS(Microsoft.JSInterop.JSRuntime jsRuntime, string arguments) { } + public static string Invoke(Microsoft.JSInterop.JSRuntime jsRuntime, string assemblyName, string methodIdentifier, long dotNetObjectId, string argsJson) { throw null; } } } diff --git a/src/JSInterop/Microsoft.JSInterop/src/DotNetObjectReference.cs b/src/JSInterop/Microsoft.JSInterop/src/DotNetObjectReference.cs index 24b13f0c85..989d8062bb 100644 --- a/src/JSInterop/Microsoft.JSInterop/src/DotNetObjectReference.cs +++ b/src/JSInterop/Microsoft.JSInterop/src/DotNetObjectReference.cs @@ -1,8 +1,6 @@ // 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 Microsoft.JSInterop.Infrastructure; - namespace Microsoft.JSInterop { /// @@ -17,7 +15,7 @@ namespace Microsoft.JSInterop /// An instance of . public static DotNetObjectReference Create(TValue value) where TValue : class { - return new DotNetObjectReference(DotNetObjectReferenceManager.Current, value); + return new DotNetObjectReference(value); } } } diff --git a/src/JSInterop/Microsoft.JSInterop/src/DotNetObjectReferenceOfT.cs b/src/JSInterop/Microsoft.JSInterop/src/DotNetObjectReferenceOfT.cs index eb1ac6a234..773c2ed9a3 100644 --- a/src/JSInterop/Microsoft.JSInterop/src/DotNetObjectReferenceOfT.cs +++ b/src/JSInterop/Microsoft.JSInterop/src/DotNetObjectReferenceOfT.cs @@ -2,7 +2,7 @@ // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; -using System.Text.Json.Serialization; +using System.Diagnostics; using Microsoft.JSInterop.Infrastructure; namespace Microsoft.JSInterop @@ -14,22 +14,18 @@ namespace Microsoft.JSInterop /// To avoid leaking memory, the reference must later be disposed by JS code or by .NET code. /// /// The type of the value to wrap. - [JsonConverter(typeof(DotNetObjectReferenceJsonConverterFactory))] public sealed class DotNetObjectReference : IDotNetObjectReference, IDisposable where TValue : class { - private readonly DotNetObjectReferenceManager _referenceManager; private readonly TValue _value; - private readonly long _objectId; + private long _objectId; + private JSRuntime _jsRuntime; /// /// Initializes a new instance of . /// - /// /// The value to pass by reference. - internal DotNetObjectReference(DotNetObjectReferenceManager referenceManager, TValue value) + internal DotNetObjectReference(TValue value) { - _referenceManager = referenceManager; - _objectId = _referenceManager.TrackObject(this); _value = value; } @@ -50,8 +46,30 @@ namespace Microsoft.JSInterop get { ThrowIfDisposed(); + Debug.Assert(_objectId != 0, "Accessing ObjectId without tracking is always incorrect."); + return _objectId; } + set + { + ThrowIfDisposed(); + _objectId = value; + } + } + + internal JSRuntime JSRuntime + { + get + { + ThrowIfDisposed(); + return _jsRuntime; + } + set + { + ThrowIfDisposed(); + _jsRuntime = value; + } + } object IDotNetObjectReference.Value => Value; @@ -68,11 +86,15 @@ namespace Microsoft.JSInterop if (!Disposed) { Disposed = true; - _referenceManager.ReleaseDotNetObject(_objectId); + + if (_jsRuntime != null) + { + _jsRuntime.ReleaseObjectReference(_objectId); + } } } - private void ThrowIfDisposed() + internal void ThrowIfDisposed() { if (Disposed) { diff --git a/src/JSInterop/Microsoft.JSInterop/src/Infrastructure/DotNetDispatcher.cs b/src/JSInterop/Microsoft.JSInterop/src/Infrastructure/DotNetDispatcher.cs index d4a4de14dd..92afc6278d 100644 --- a/src/JSInterop/Microsoft.JSInterop/src/Infrastructure/DotNetDispatcher.cs +++ b/src/JSInterop/Microsoft.JSInterop/src/Infrastructure/DotNetDispatcher.cs @@ -27,12 +27,13 @@ namespace Microsoft.JSInterop.Infrastructure /// /// Receives a call from JS to .NET, locating and invoking the specified method. /// + /// The . /// The assembly containing the method to be invoked. /// The identifier of the method to be invoked. The method must be annotated with a matching this identifier string. /// For instance method calls, identifies the target object. /// A JSON representation of the parameters. /// A JSON representation of the return value, or null. - public static string Invoke(string assemblyName, string methodIdentifier, long dotNetObjectId, string argsJson) + public static string Invoke(JSRuntime jsRuntime, string assemblyName, string methodIdentifier, long dotNetObjectId, string argsJson) { // This method doesn't need [JSInvokable] because the platform is responsible for having // some way to dispatch calls here. The logic inside here is the thing that checks whether @@ -42,41 +43,38 @@ namespace Microsoft.JSInterop.Infrastructure IDotNetObjectReference targetInstance = default; if (dotNetObjectId != default) { - targetInstance = DotNetObjectReferenceManager.Current.FindDotNetObject(dotNetObjectId); + targetInstance = jsRuntime.GetObjectReference(dotNetObjectId); } - var syncResult = InvokeSynchronously(assemblyName, methodIdentifier, targetInstance, argsJson); + var syncResult = InvokeSynchronously(jsRuntime, assemblyName, methodIdentifier, targetInstance, argsJson); if (syncResult == null) { return null; } - return JsonSerializer.Serialize(syncResult, JsonSerializerOptionsProvider.Options); + return JsonSerializer.Serialize(syncResult, jsRuntime.JsonSerializerOptions); } /// /// Receives a call from JS to .NET, locating and invoking the specified method asynchronously. /// + /// The . /// A value identifying the asynchronous call that should be passed back with the result, or null if no result notification is required. /// The assembly containing the method to be invoked. /// The identifier of the method to be invoked. The method must be annotated with a matching this identifier string. /// For instance method calls, identifies the target object. /// A JSON representation of the parameters. /// A JSON representation of the return value, or null. - public static void BeginInvokeDotNet(string callId, string assemblyName, string methodIdentifier, long dotNetObjectId, string argsJson) + public static void BeginInvokeDotNet(JSRuntime jsRuntime, string callId, string assemblyName, string methodIdentifier, long dotNetObjectId, string argsJson) { // This method doesn't need [JSInvokable] because the platform is responsible for having // some way to dispatch calls here. The logic inside here is the thing that checks whether // the targeted method has [JSInvokable]. It is not itself subject to that restriction, // because there would be nobody to police that. This method *is* the police. - // DotNetDispatcher only works with JSRuntimeBase instances. - // If the developer wants to use a totally custom IJSRuntime, then their JS-side - // code has to implement its own way of returning async results. - var jsRuntimeBaseInstance = (JSRuntime)JSRuntime.Current; - // Using ExceptionDispatchInfo here throughout because we want to always preserve // original stack traces. + object syncResult = null; ExceptionDispatchInfo syncException = null; IDotNetObjectReference targetInstance = null; @@ -85,10 +83,10 @@ namespace Microsoft.JSInterop.Infrastructure { if (dotNetObjectId != default) { - targetInstance = DotNetObjectReferenceManager.Current.FindDotNetObject(dotNetObjectId); + targetInstance = jsRuntime.GetObjectReference(dotNetObjectId); } - syncResult = InvokeSynchronously(assemblyName, methodIdentifier, targetInstance, argsJson); + syncResult = InvokeSynchronously(jsRuntime, assemblyName, methodIdentifier, targetInstance, argsJson); } catch (Exception ex) { @@ -103,7 +101,7 @@ namespace Microsoft.JSInterop.Infrastructure else if (syncException != null) { // Threw synchronously, let's respond. - jsRuntimeBaseInstance.EndInvokeDotNet(callId, false, syncException, assemblyName, methodIdentifier, dotNetObjectId); + jsRuntime.EndInvokeDotNet(callId, false, syncException, assemblyName, methodIdentifier, dotNetObjectId); } else if (syncResult is Task task) { @@ -115,20 +113,20 @@ namespace Microsoft.JSInterop.Infrastructure { var exception = t.Exception.GetBaseException(); - jsRuntimeBaseInstance.EndInvokeDotNet(callId, false, ExceptionDispatchInfo.Capture(exception), assemblyName, methodIdentifier, dotNetObjectId); + jsRuntime.EndInvokeDotNet(callId, false, ExceptionDispatchInfo.Capture(exception), assemblyName, methodIdentifier, dotNetObjectId); } var result = TaskGenericsUtil.GetTaskResult(task); - jsRuntimeBaseInstance.EndInvokeDotNet(callId, true, result, assemblyName, methodIdentifier, dotNetObjectId); + jsRuntime.EndInvokeDotNet(callId, true, result, assemblyName, methodIdentifier, dotNetObjectId); }, TaskScheduler.Current); } else { - jsRuntimeBaseInstance.EndInvokeDotNet(callId, true, syncResult, assemblyName, methodIdentifier, dotNetObjectId); + jsRuntime.EndInvokeDotNet(callId, true, syncResult, assemblyName, methodIdentifier, dotNetObjectId); } } - private static object InvokeSynchronously(string assemblyName, string methodIdentifier, IDotNetObjectReference objectReference, string argsJson) + private static object InvokeSynchronously(JSRuntime jsRuntime, string assemblyName, string methodIdentifier, IDotNetObjectReference objectReference, string argsJson) { AssemblyKey assemblyKey; if (objectReference is null) @@ -154,7 +152,7 @@ namespace Microsoft.JSInterop.Infrastructure var (methodInfo, parameterTypes) = GetCachedMethodInfo(assemblyKey, methodIdentifier); - var suppliedArgs = ParseArguments(methodIdentifier, argsJson, parameterTypes); + var suppliedArgs = ParseArguments(jsRuntime, methodIdentifier, argsJson, parameterTypes); try { @@ -173,7 +171,7 @@ namespace Microsoft.JSInterop.Infrastructure } } - internal static object[] ParseArguments(string methodIdentifier, string arguments, Type[] parameterTypes) + internal static object[] ParseArguments(JSRuntime jsRuntime, string methodIdentifier, string arguments, Type[] parameterTypes) { if (parameterTypes.Length == 0) { @@ -198,7 +196,7 @@ namespace Microsoft.JSInterop.Infrastructure throw new InvalidOperationException($"In call to '{methodIdentifier}', parameter of type '{parameterType.Name}' at index {(index + 1)} must be declared as type 'DotNetObjectRef<{parameterType.Name}>' to receive the incoming value."); } - suppliedArgs[index] = JsonSerializer.Deserialize(ref reader, parameterType, JsonSerializerOptionsProvider.Options); + suppliedArgs[index] = JsonSerializer.Deserialize(ref reader, parameterType, jsRuntime.JsonSerializerOptions); index++; } @@ -247,18 +245,13 @@ namespace Microsoft.JSInterop.Infrastructure /// method is responsible for handling any possible exception generated from the arguments /// passed in as parameters. /// + /// The . /// The serialized arguments for the callback completion. /// /// This method can throw any exception either from the argument received or as a result /// of executing any callback synchronously upon completion. /// - public static void EndInvokeJS(string arguments) - { - var jsRuntimeBase = (JSRuntime)JSRuntime.Current; - ParseEndInvokeArguments(jsRuntimeBase, arguments); - } - - internal static void ParseEndInvokeArguments(JSRuntime jsRuntimeBase, string arguments) + public static void EndInvokeJS(JSRuntime jsRuntime, string arguments) { var utf8JsonBytes = Encoding.UTF8.GetBytes(arguments); @@ -281,7 +274,7 @@ namespace Microsoft.JSInterop.Infrastructure var success = reader.GetBoolean(); reader.Read(); - jsRuntimeBase.EndInvokeJS(taskId, success, ref reader); + jsRuntime.EndInvokeJS(taskId, success, ref reader); if (!reader.Read() || reader.TokenType != JsonTokenType.EndArray) { diff --git a/src/JSInterop/Microsoft.JSInterop/src/Infrastructure/DotNetObjectReferenceJsonConverter.cs b/src/JSInterop/Microsoft.JSInterop/src/Infrastructure/DotNetObjectReferenceJsonConverter.cs index c077ac0b17..7658bbc2c3 100644 --- a/src/JSInterop/Microsoft.JSInterop/src/Infrastructure/DotNetObjectReferenceJsonConverter.cs +++ b/src/JSInterop/Microsoft.JSInterop/src/Infrastructure/DotNetObjectReferenceJsonConverter.cs @@ -9,8 +9,15 @@ namespace Microsoft.JSInterop.Infrastructure { internal sealed class DotNetObjectReferenceJsonConverter : JsonConverter> where TValue : class { + public DotNetObjectReferenceJsonConverter(JSRuntime jsRuntime) + { + JSRuntime = jsRuntime; + } + private static JsonEncodedText DotNetObjectRefKey => DotNetDispatcher.DotNetObjectRefKey; + public JSRuntime JSRuntime { get; } + public override DotNetObjectReference Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) { long dotNetObjectId = 0; @@ -40,14 +47,16 @@ namespace Microsoft.JSInterop.Infrastructure throw new JsonException($"Required property {DotNetObjectRefKey} not found."); } - var referenceManager = DotNetObjectReferenceManager.Current; - return (DotNetObjectReference)referenceManager.FindDotNetObject(dotNetObjectId); + var value = (DotNetObjectReference)JSRuntime.GetObjectReference(dotNetObjectId); + return value; } public override void Write(Utf8JsonWriter writer, DotNetObjectReference value, JsonSerializerOptions options) { + var objectId = JSRuntime.TrackObjectReference(value); + writer.WriteStartObject(); - writer.WriteNumber(DotNetObjectRefKey, value.ObjectId); + writer.WriteNumber(DotNetObjectRefKey, objectId); writer.WriteEndObject(); } } diff --git a/src/JSInterop/Microsoft.JSInterop/src/Infrastructure/DotNetObjectReferenceJsonConverterFactory.cs b/src/JSInterop/Microsoft.JSInterop/src/Infrastructure/DotNetObjectReferenceJsonConverterFactory.cs index 350530b624..288bfdd090 100644 --- a/src/JSInterop/Microsoft.JSInterop/src/Infrastructure/DotNetObjectReferenceJsonConverterFactory.cs +++ b/src/JSInterop/Microsoft.JSInterop/src/Infrastructure/DotNetObjectReferenceJsonConverterFactory.cs @@ -9,6 +9,13 @@ namespace Microsoft.JSInterop.Infrastructure { internal sealed class DotNetObjectReferenceJsonConverterFactory : JsonConverterFactory { + public DotNetObjectReferenceJsonConverterFactory(JSRuntime jsRuntime) + { + JSRuntime = jsRuntime; + } + + public JSRuntime JSRuntime { get; } + public override bool CanConvert(Type typeToConvert) { return typeToConvert.IsGenericType && typeToConvert.GetGenericTypeDefinition() == typeof(DotNetObjectReference<>); @@ -20,7 +27,7 @@ namespace Microsoft.JSInterop.Infrastructure var instanceType = typeToConvert.GetGenericArguments()[0]; var converterType = typeof(DotNetObjectReferenceJsonConverter<>).MakeGenericType(instanceType); - return (JsonConverter)Activator.CreateInstance(converterType); + return (JsonConverter)Activator.CreateInstance(converterType, JSRuntime); } } } diff --git a/src/JSInterop/Microsoft.JSInterop/src/Infrastructure/DotNetObjectReferenceManager.cs b/src/JSInterop/Microsoft.JSInterop/src/Infrastructure/DotNetObjectReferenceManager.cs deleted file mode 100644 index 709dd963fa..0000000000 --- a/src/JSInterop/Microsoft.JSInterop/src/Infrastructure/DotNetObjectReferenceManager.cs +++ /dev/null @@ -1,51 +0,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.Collections.Concurrent; -using System.Threading; - -namespace Microsoft.JSInterop.Infrastructure -{ - internal class DotNetObjectReferenceManager - { - private long _nextId = 0; // 0 signals no object, but we increment prior to assignment. The first tracked object should have id 1 - private readonly ConcurrentDictionary _trackedRefsById = new ConcurrentDictionary(); - - public static DotNetObjectReferenceManager Current - { - get - { - if (!(JSRuntime.Current is JSRuntime jsRuntime)) - { - throw new InvalidOperationException("JSRuntime must be set up correctly and must be an instance of JSRuntimeBase to use DotNetObjectReference."); - } - - return jsRuntime.ObjectRefManager; - } - } - - public long TrackObject(IDotNetObjectReference dotNetObjectRef) - { - var dotNetObjectId = Interlocked.Increment(ref _nextId); - _trackedRefsById[dotNetObjectId] = dotNetObjectRef; - - return dotNetObjectId; - } - - public IDotNetObjectReference FindDotNetObject(long dotNetObjectId) - { - return _trackedRefsById.TryGetValue(dotNetObjectId, out var dotNetObjectRef) - ? dotNetObjectRef - : throw new ArgumentException($"There is no tracked object with id '{dotNetObjectId}'. Perhaps the DotNetObjectRef instance was already disposed.", nameof(dotNetObjectId)); - - } - - /// - /// Stops tracking the specified .NET object reference. - /// This may be invoked either by disposing a DotNetObjectRef in .NET code, or via JS interop by calling "dispose" on the corresponding instance in JavaScript code - /// - /// The ID of the . - public void ReleaseDotNetObject(long dotNetObjectId) => _trackedRefsById.TryRemove(dotNetObjectId, out _); - } -} diff --git a/src/JSInterop/Microsoft.JSInterop/src/JSInProcessRuntime.cs b/src/JSInterop/Microsoft.JSInterop/src/JSInProcessRuntime.cs index cf8cc7030b..2b96bbbbb5 100644 --- a/src/JSInterop/Microsoft.JSInterop/src/JSInProcessRuntime.cs +++ b/src/JSInterop/Microsoft.JSInterop/src/JSInProcessRuntime.cs @@ -19,13 +19,13 @@ namespace Microsoft.JSInterop /// An instance of obtained by JSON-deserializing the return value. public TValue Invoke(string identifier, params object[] args) { - var resultJson = InvokeJS(identifier, JsonSerializer.Serialize(args, JsonSerializerOptionsProvider.Options)); + var resultJson = InvokeJS(identifier, JsonSerializer.Serialize(args, JsonSerializerOptions)); if (resultJson is null) { return default; } - return JsonSerializer.Deserialize(resultJson, JsonSerializerOptionsProvider.Options); + return JsonSerializer.Deserialize(resultJson, JsonSerializerOptions); } /// diff --git a/src/JSInterop/Microsoft.JSInterop/src/JSRuntime.cs b/src/JSInterop/Microsoft.JSInterop/src/JSRuntime.cs index 598b47c4d4..ba411b72db 100644 --- a/src/JSInterop/Microsoft.JSInterop/src/JSRuntime.cs +++ b/src/JSInterop/Microsoft.JSInterop/src/JSRuntime.cs @@ -3,6 +3,7 @@ using System; using System.Collections.Concurrent; +using System.Diagnostics; using System.Linq; using System.Text.Json; using System.Threading; @@ -16,36 +17,40 @@ namespace Microsoft.JSInterop /// public abstract partial class JSRuntime : IJSRuntime { - private static readonly AsyncLocal _currentJSRuntime = new AsyncLocal(); - - internal static IJSRuntime Current => _currentJSRuntime.Value; - + private long _nextObjectReferenceId = 0; // 0 signals no object, but we increment prior to assignment. The first tracked object should have id 1 private long _nextPendingTaskId = 1; // Start at 1 because zero signals "no response needed" - private readonly ConcurrentDictionary _pendingTasks - = new ConcurrentDictionary(); - + private readonly ConcurrentDictionary _pendingTasks = new ConcurrentDictionary(); + private readonly ConcurrentDictionary _trackedRefsById = new ConcurrentDictionary(); private readonly ConcurrentDictionary _cancellationRegistrations = new ConcurrentDictionary(); - internal DotNetObjectReferenceManager ObjectRefManager { get; } = new DotNetObjectReferenceManager(); + /// + /// Initializes a new instance of . + /// + protected JSRuntime() + { + JsonSerializerOptions = new JsonSerializerOptions + { + MaxDepth = 32, + PropertyNamingPolicy = JsonNamingPolicy.CamelCase, + PropertyNameCaseInsensitive = true, + Converters = + { + new DotNetObjectReferenceJsonConverterFactory(this), + } + }; + } + + /// + /// Gets the used to serialize and deserialize interop payloads. + /// + protected internal JsonSerializerOptions JsonSerializerOptions { get; } /// /// Gets or sets the default timeout for asynchronous JavaScript calls. /// protected TimeSpan? DefaultAsyncTimeout { get; set; } - /// - /// Sets the current JS runtime to the supplied instance. - /// - /// This is intended for framework use. Developers should not normally need to call this method. - /// - /// The new current . - public static void SetCurrentJSRuntime(IJSRuntime instance) - { - _currentJSRuntime.Value = instance - ?? throw new ArgumentNullException(nameof(instance)); - } - /// /// Invokes the specified JavaScript function asynchronously. /// @@ -103,7 +108,7 @@ namespace Microsoft.JSInterop } var argsJson = args?.Any() == true ? - JsonSerializer.Serialize(args, JsonSerializerOptionsProvider.Options) : + JsonSerializer.Serialize(args, JsonSerializerOptions) : null; BeginInvokeJS(taskId, identifier, argsJson); @@ -176,7 +181,7 @@ namespace Microsoft.JSInterop { var resultType = TaskGenericsUtil.GetTaskCompletionSourceResultType(tcs); - var result = JsonSerializer.Deserialize(ref jsonReader, resultType, JsonSerializerOptionsProvider.Options); + var result = JsonSerializer.Deserialize(ref jsonReader, resultType, JsonSerializerOptions); TaskGenericsUtil.SetTaskCompletionSourceResult(tcs, result); } else @@ -191,5 +196,48 @@ namespace Microsoft.JSInterop TaskGenericsUtil.SetTaskCompletionSourceException(tcs, new JSException(message, exception)); } } + + internal long TrackObjectReference(DotNetObjectReference dotNetObjectReference) where TValue : class + { + if (dotNetObjectReference == null) + { + throw new ArgumentNullException(nameof(dotNetObjectReference)); + } + + dotNetObjectReference.ThrowIfDisposed(); + + var jsRuntime = dotNetObjectReference.JSRuntime; + if (jsRuntime is null) + { + var dotNetObjectId = Interlocked.Increment(ref _nextObjectReferenceId); + + dotNetObjectReference.JSRuntime = this; + dotNetObjectReference.ObjectId = dotNetObjectId; + + _trackedRefsById[dotNetObjectId] = dotNetObjectReference; + } + else if (!ReferenceEquals(this, jsRuntime)) + { + throw new InvalidOperationException($"{dotNetObjectReference.GetType().Name} is already being tracked by a different instance of {nameof(JSRuntime)}." + + $" A common cause is caching an instance of {nameof(DotNetObjectReference)} globally. Consider creating instances of {nameof(DotNetObjectReference)} at the JSInterop callsite."); + } + + Debug.Assert(dotNetObjectReference.ObjectId != 0); + return dotNetObjectReference.ObjectId; + } + + internal IDotNetObjectReference GetObjectReference(long dotNetObjectId) + { + return _trackedRefsById.TryGetValue(dotNetObjectId, out var dotNetObjectRef) + ? dotNetObjectRef + : throw new ArgumentException($"There is no tracked object with id '{dotNetObjectId}'. Perhaps the DotNetObjectReference instance was already disposed.", nameof(dotNetObjectId)); + } + + /// + /// Stops tracking the specified .NET object reference. + /// This may be invoked either by disposing a DotNetObjectRef in .NET code, or via JS interop by calling "dispose" on the corresponding instance in JavaScript code + /// + /// The ID of the . + internal void ReleaseObjectReference(long dotNetObjectId) => _trackedRefsById.TryRemove(dotNetObjectId, out _); } } diff --git a/src/JSInterop/Microsoft.JSInterop/src/JsonSerializerOptionsProvider.cs b/src/JSInterop/Microsoft.JSInterop/src/JsonSerializerOptionsProvider.cs deleted file mode 100644 index 62244270e3..0000000000 --- a/src/JSInterop/Microsoft.JSInterop/src/JsonSerializerOptionsProvider.cs +++ /dev/null @@ -1,17 +0,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.Text.Json; - -namespace Microsoft.JSInterop -{ - internal static class JsonSerializerOptionsProvider - { - public static readonly JsonSerializerOptions Options = new JsonSerializerOptions - { - MaxDepth = 32, - PropertyNamingPolicy = JsonNamingPolicy.CamelCase, - PropertyNameCaseInsensitive = true, - }; - } -} diff --git a/src/JSInterop/Microsoft.JSInterop/test/DotNetObjectReferenceTest.cs b/src/JSInterop/Microsoft.JSInterop/test/DotNetObjectReferenceTest.cs index bcd5c95028..95fad485a7 100644 --- a/src/JSInterop/Microsoft.JSInterop/test/DotNetObjectReferenceTest.cs +++ b/src/JSInterop/Microsoft.JSInterop/test/DotNetObjectReferenceTest.cs @@ -2,34 +2,102 @@ // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; -using System.Threading.Tasks; using Xunit; -using static Microsoft.JSInterop.TestJSRuntime; namespace Microsoft.JSInterop { public class DotNetObjectReferenceTest { [Fact] - public Task CanAccessValue() => WithJSRuntime(_ => + public void CanAccessValue() { var obj = new object(); Assert.Same(obj, DotNetObjectReference.Create(obj).Value); - }); + } [Fact] - public Task NotifiesAssociatedJsRuntimeOfDisposal() => WithJSRuntime(jsRuntime => + public void TrackObjectReference_AssignsObjectId() { // Arrange + var jsRuntime = new TestJSRuntime(); var objRef = DotNetObjectReference.Create(new object()); // Act + var objectId = jsRuntime.TrackObjectReference(objRef); + + // Act + Assert.Equal(objectId, objRef.ObjectId); Assert.Equal(1, objRef.ObjectId); + } + + [Fact] + public void TrackObjectReference_AllowsMultipleCallsUsingTheSameJSRuntime() + { + // Arrange + var jsRuntime = new TestJSRuntime(); + var objRef = DotNetObjectReference.Create(new object()); + + // Act + var objectId1 = jsRuntime.TrackObjectReference(objRef); + var objectId2 = jsRuntime.TrackObjectReference(objRef); + + // Act + Assert.Equal(objectId1, objectId2); + } + + [Fact] + public void TrackObjectReference_ThrowsIfDifferentJSRuntimeInstancesAreUsed() + { + // Arrange + var objRef = DotNetObjectReference.Create("Hello world"); + var expected = $"{objRef.GetType().Name} is already being tracked by a different instance of {nameof(JSRuntime)}. A common cause is caching an instance of {nameof(DotNetObjectReference)}" + + $" globally. Consider creating instances of {nameof(DotNetObjectReference)} at the JSInterop callsite."; + var jsRuntime1 = new TestJSRuntime(); + var jsRuntime2 = new TestJSRuntime(); + jsRuntime1.TrackObjectReference(objRef); + + // Act + var ex = Assert.Throws(() => jsRuntime2.TrackObjectReference(objRef)); + + // Assert + Assert.Equal(expected, ex.Message); + } + + [Fact] + public void Dispose_StopsTrackingObject() + { + // Arrange + var objRef = DotNetObjectReference.Create("Hello world"); + var jsRuntime = new TestJSRuntime(); + jsRuntime.TrackObjectReference(objRef); + var objectId = objRef.ObjectId; + var expected = $"There is no tracked object with id '{objectId}'. Perhaps the DotNetObjectReference instance was already disposed."; + + // Act + Assert.Same(objRef, jsRuntime.GetObjectReference(objectId)); objRef.Dispose(); // Assert - var ex = Assert.Throws(() => jsRuntime.ObjectRefManager.FindDotNetObject(1)); - Assert.StartsWith("There is no tracked object with id '1'.", ex.Message); - }); + Assert.True(objRef.Disposed); + Assert.Throws(() => jsRuntime.GetObjectReference(objectId)); + } + + [Fact] + public void DoubleDispose_Works() + { + // Arrange + var objRef = DotNetObjectReference.Create("Hello world"); + var jsRuntime = new TestJSRuntime(); + jsRuntime.TrackObjectReference(objRef); + var objectId = objRef.ObjectId; + + // Act + Assert.Same(objRef, jsRuntime.GetObjectReference(objectId)); + objRef.Dispose(); + + // Assert + objRef.Dispose(); + // If we got this far, this did not throw. + } } } diff --git a/src/JSInterop/Microsoft.JSInterop/test/Infrastructure/DotNetDispatcherTest.cs b/src/JSInterop/Microsoft.JSInterop/test/Infrastructure/DotNetDispatcherTest.cs index d9ddac2a89..2d8208b7a6 100644 --- a/src/JSInterop/Microsoft.JSInterop/test/Infrastructure/DotNetDispatcherTest.cs +++ b/src/JSInterop/Microsoft.JSInterop/test/Infrastructure/DotNetDispatcherTest.cs @@ -20,7 +20,7 @@ namespace Microsoft.JSInterop.Infrastructure { var ex = Assert.Throws(() => { - DotNetDispatcher.Invoke(" ", "SomeMethod", default, "[]"); + DotNetDispatcher.Invoke(new TestJSRuntime(), " ", "SomeMethod", default, "[]"); }); Assert.StartsWith("Cannot be null, empty, or whitespace.", ex.Message); @@ -32,7 +32,7 @@ namespace Microsoft.JSInterop.Infrastructure { var ex = Assert.Throws(() => { - DotNetDispatcher.Invoke("SomeAssembly", " ", default, "[]"); + DotNetDispatcher.Invoke(new TestJSRuntime(), "SomeAssembly", " ", default, "[]"); }); Assert.StartsWith("Cannot be null, empty, or whitespace.", ex.Message); @@ -45,7 +45,7 @@ namespace Microsoft.JSInterop.Infrastructure var assemblyName = "Some.Fake.Assembly"; var ex = Assert.Throws(() => { - DotNetDispatcher.Invoke(assemblyName, "SomeMethod", default, null); + DotNetDispatcher.Invoke(new TestJSRuntime(), assemblyName, "SomeMethod", default, null); }); Assert.Equal($"There is no loaded assembly with the name '{assemblyName}'.", ex.Message); @@ -67,52 +67,56 @@ namespace Microsoft.JSInterop.Infrastructure { var ex = Assert.Throws(() => { - DotNetDispatcher.Invoke(thisAssemblyName, methodIdentifier, default, null); + DotNetDispatcher.Invoke(new TestJSRuntime(), thisAssemblyName, methodIdentifier, default, null); }); Assert.Equal($"The assembly '{thisAssemblyName}' does not contain a public method with [JSInvokableAttribute(\"{methodIdentifier}\")].", ex.Message); } [Fact] - public Task CanInvokeStaticVoidMethod() => WithJSRuntime(jsRuntime => + public void CanInvokeStaticVoidMethod() { // Arrange/Act + var jsRuntime = new TestJSRuntime(); SomePublicType.DidInvokeMyInvocableStaticVoid = false; - var resultJson = DotNetDispatcher.Invoke(thisAssemblyName, "InvocableStaticVoid", default, null); + var resultJson = DotNetDispatcher.Invoke(jsRuntime, thisAssemblyName, "InvocableStaticVoid", default, null); // Assert Assert.Null(resultJson); Assert.True(SomePublicType.DidInvokeMyInvocableStaticVoid); - }); + } [Fact] - public Task CanInvokeStaticNonVoidMethod() => WithJSRuntime(jsRuntime => + public void CanInvokeStaticNonVoidMethod() { // Arrange/Act - var resultJson = DotNetDispatcher.Invoke(thisAssemblyName, "InvocableStaticNonVoid", default, null); - var result = JsonSerializer.Deserialize(resultJson, JsonSerializerOptionsProvider.Options); + var jsRuntime = new TestJSRuntime(); + var resultJson = DotNetDispatcher.Invoke(jsRuntime, thisAssemblyName, "InvocableStaticNonVoid", default, null); + var result = JsonSerializer.Deserialize(resultJson, jsRuntime.JsonSerializerOptions); // Assert Assert.Equal("Test", result.StringVal); Assert.Equal(123, result.IntVal); - }); + } [Fact] - public Task CanInvokeStaticNonVoidMethodWithoutCustomIdentifier() => WithJSRuntime(jsRuntime => + public void CanInvokeStaticNonVoidMethodWithoutCustomIdentifier() { // Arrange/Act - var resultJson = DotNetDispatcher.Invoke(thisAssemblyName, nameof(SomePublicType.InvokableMethodWithoutCustomIdentifier), default, null); - var result = JsonSerializer.Deserialize(resultJson, JsonSerializerOptionsProvider.Options); + var jsRuntime = new TestJSRuntime(); + var resultJson = DotNetDispatcher.Invoke(jsRuntime, thisAssemblyName, nameof(SomePublicType.InvokableMethodWithoutCustomIdentifier), default, null); + var result = JsonSerializer.Deserialize(resultJson, jsRuntime.JsonSerializerOptions); // Assert Assert.Equal("InvokableMethodWithoutCustomIdentifier", result.StringVal); Assert.Equal(456, result.IntVal); - }); + } [Fact] - public Task CanInvokeStaticWithParams() => WithJSRuntime(jsRuntime => + public void CanInvokeStaticWithParams() { // Arrange: Track a .NET object to use as an arg + var jsRuntime = new TestJSRuntime(); var arg3 = new TestDTO { IntVal = 999, StringVal = "My string" }; var objectRef = DotNetObjectReference.Create(arg3); jsRuntime.Invoke("unimportant", objectRef); @@ -123,15 +127,15 @@ namespace Microsoft.JSInterop.Infrastructure new TestDTO { StringVal = "Another string", IntVal = 456 }, new[] { 100, 200 }, objectRef - }, JsonSerializerOptionsProvider.Options); + }, jsRuntime.JsonSerializerOptions); // Act - var resultJson = DotNetDispatcher.Invoke(thisAssemblyName, "InvocableStaticWithParams", default, argsJson); + var resultJson = DotNetDispatcher.Invoke(jsRuntime, thisAssemblyName, "InvocableStaticWithParams", default, argsJson); var result = JsonDocument.Parse(resultJson); var root = result.RootElement; // Assert: First result value marshalled via JSON - var resultDto1 = JsonSerializer.Deserialize(root[0].GetRawText(), JsonSerializerOptionsProvider.Options); + var resultDto1 = JsonSerializer.Deserialize(root[0].GetRawText(), jsRuntime.JsonSerializerOptions); Assert.Equal("ANOTHER STRING", resultDto1.StringVal); Assert.Equal(756, resultDto1.IntVal); @@ -142,15 +146,16 @@ namespace Microsoft.JSInterop.Infrastructure Assert.False(resultDto2Ref.TryGetProperty(nameof(TestDTO.IntVal), out _)); Assert.True(resultDto2Ref.TryGetProperty(DotNetDispatcher.DotNetObjectRefKey.EncodedUtf8Bytes, out var property)); - var resultDto2 = Assert.IsType>(DotNetObjectReferenceManager.Current.FindDotNetObject(property.GetInt64())).Value; + var resultDto2 = Assert.IsType>(jsRuntime.GetObjectReference(property.GetInt64())).Value; Assert.Equal("MY STRING", resultDto2.StringVal); Assert.Equal(1299, resultDto2.IntVal); - }); + } [Fact] - public Task InvokingWithIncorrectUseOfDotNetObjectRefThrows() => WithJSRuntime(jsRuntime => + public void InvokingWithIncorrectUseOfDotNetObjectRefThrows() { // Arrange + var jsRuntime = new TestJSRuntime(); var method = nameof(SomePublicType.IncorrectDotNetObjectRefUsage); var arg3 = new TestDTO { IntVal = 999, StringVal = "My string" }; var objectRef = DotNetObjectReference.Create(arg3); @@ -162,67 +167,72 @@ namespace Microsoft.JSInterop.Infrastructure new TestDTO { StringVal = "Another string", IntVal = 456 }, new[] { 100, 200 }, objectRef - }, JsonSerializerOptionsProvider.Options); + }, jsRuntime.JsonSerializerOptions); // Act & Assert var ex = Assert.Throws(() => - DotNetDispatcher.Invoke(thisAssemblyName, method, default, argsJson)); + DotNetDispatcher.Invoke(jsRuntime, thisAssemblyName, method, default, argsJson)); Assert.Equal($"In call to '{method}', parameter of type '{nameof(TestDTO)}' at index 3 must be declared as type 'DotNetObjectRef' to receive the incoming value.", ex.Message); - }); + } [Fact] - public Task CanInvokeInstanceVoidMethod() => WithJSRuntime(jsRuntime => + public void CanInvokeInstanceVoidMethod() { // Arrange: Track some instance + var jsRuntime = new TestJSRuntime(); var targetInstance = new SomePublicType(); var objectRef = DotNetObjectReference.Create(targetInstance); jsRuntime.Invoke("unimportant", objectRef); // Act - var resultJson = DotNetDispatcher.Invoke(null, "InvokableInstanceVoid", 1, null); + var resultJson = DotNetDispatcher.Invoke(jsRuntime, null, "InvokableInstanceVoid", 1, null); // Assert Assert.Null(resultJson); Assert.True(targetInstance.DidInvokeMyInvocableInstanceVoid); - }); + } [Fact] - public Task CanInvokeBaseInstanceVoidMethod() => WithJSRuntime(jsRuntime => + public void CanInvokeBaseInstanceVoidMethod() { // Arrange: Track some instance + var jsRuntime = new TestJSRuntime(); var targetInstance = new DerivedClass(); var objectRef = DotNetObjectReference.Create(targetInstance); jsRuntime.Invoke("unimportant", objectRef); // Act - var resultJson = DotNetDispatcher.Invoke(null, "BaseClassInvokableInstanceVoid", 1, null); + var resultJson = DotNetDispatcher.Invoke(jsRuntime, null, "BaseClassInvokableInstanceVoid", 1, null); // Assert Assert.Null(resultJson); Assert.True(targetInstance.DidInvokeMyBaseClassInvocableInstanceVoid); - }); + } [Fact] - public Task DotNetObjectReferencesCanBeDisposed() => WithJSRuntime(jsRuntime => + public void DotNetObjectReferencesCanBeDisposed() { // Arrange + var jsRuntime = new TestJSRuntime(); var targetInstance = new SomePublicType(); var objectRef = DotNetObjectReference.Create(targetInstance); + jsRuntime.Invoke("unimportant", objectRef); // Act - DotNetDispatcher.BeginInvokeDotNet(null, null, "__Dispose", objectRef.ObjectId, null); + DotNetDispatcher.BeginInvokeDotNet(jsRuntime, null, null, "__Dispose", objectRef.ObjectId, null); // Assert Assert.True(objectRef.Disposed); - }); + } [Fact] - public Task CannotUseDotNetObjectRefAfterDisposal() => WithJSRuntime(jsRuntime => + public void CannotUseDotNetObjectRefAfterDisposal() { // This test addresses the case where the developer calls objectRef.Dispose() // from .NET code, as opposed to .dispose() from JS code // Arrange: Track some instance, then dispose it + var jsRuntime = new TestJSRuntime(); var targetInstance = new SomePublicType(); var objectRef = DotNetObjectReference.Create(targetInstance); jsRuntime.Invoke("unimportant", objectRef); @@ -230,17 +240,18 @@ namespace Microsoft.JSInterop.Infrastructure // Act/Assert var ex = Assert.Throws( - () => DotNetDispatcher.Invoke(null, "InvokableInstanceVoid", 1, null)); + () => DotNetDispatcher.Invoke(jsRuntime, null, "InvokableInstanceVoid", 1, null)); Assert.StartsWith("There is no tracked object with id '1'.", ex.Message); - }); + } [Fact] - public Task CannotUseDotNetObjectRefAfterReleaseDotNetObject() => WithJSRuntime(jsRuntime => + public void CannotUseDotNetObjectRefAfterReleaseDotNetObject() { // This test addresses the case where the developer calls .dispose() // from JS code, as opposed to objectRef.Dispose() from .NET code // Arrange: Track some instance, then dispose it + var jsRuntime = new TestJSRuntime(); var targetInstance = new SomePublicType(); var objectRef = DotNetObjectReference.Create(targetInstance); jsRuntime.Invoke("unimportant", objectRef); @@ -248,80 +259,85 @@ namespace Microsoft.JSInterop.Infrastructure // Act/Assert var ex = Assert.Throws( - () => DotNetDispatcher.Invoke(null, "InvokableInstanceVoid", 1, null)); + () => DotNetDispatcher.Invoke(jsRuntime, null, "InvokableInstanceVoid", 1, null)); Assert.StartsWith("There is no tracked object with id '1'.", ex.Message); - }); + } [Fact] - public Task EndInvoke_WithSuccessValue() => WithJSRuntime(jsRuntime => + public void EndInvoke_WithSuccessValue() { // Arrange + var jsRuntime = new TestJSRuntime(); var testDTO = new TestDTO { StringVal = "Hello", IntVal = 4 }; var task = jsRuntime.InvokeAsync("unimportant"); - var argsJson = JsonSerializer.Serialize(new object[] { jsRuntime.LastInvocationAsyncHandle, true, testDTO }, JsonSerializerOptionsProvider.Options); + var argsJson = JsonSerializer.Serialize(new object[] { jsRuntime.LastInvocationAsyncHandle, true, testDTO }, jsRuntime.JsonSerializerOptions); // Act - DotNetDispatcher.EndInvokeJS(argsJson); + DotNetDispatcher.EndInvokeJS(jsRuntime, argsJson); // Assert Assert.True(task.IsCompletedSuccessfully); var result = task.Result; Assert.Equal(testDTO.StringVal, result.StringVal); Assert.Equal(testDTO.IntVal, result.IntVal); - }); + } [Fact] - public Task EndInvoke_WithErrorString() => WithJSRuntime(async jsRuntime => + public async Task EndInvoke_WithErrorString() { // Arrange + var jsRuntime = new TestJSRuntime(); var expected = "Some error"; var task = jsRuntime.InvokeAsync("unimportant"); - var argsJson = JsonSerializer.Serialize(new object[] { jsRuntime.LastInvocationAsyncHandle, false, expected }, JsonSerializerOptionsProvider.Options); + var argsJson = JsonSerializer.Serialize(new object[] { jsRuntime.LastInvocationAsyncHandle, false, expected }, jsRuntime.JsonSerializerOptions); // Act - DotNetDispatcher.EndInvokeJS(argsJson); + DotNetDispatcher.EndInvokeJS(jsRuntime, argsJson); // Assert var ex = await Assert.ThrowsAsync(async () => await task); Assert.Equal(expected, ex.Message); - }); + } [Fact(Skip = "https://github.com/aspnet/AspNetCore/issues/12357")] - public Task EndInvoke_AfterCancel() => WithJSRuntime(jsRuntime => + public void EndInvoke_AfterCancel() { // Arrange + var jsRuntime = new TestJSRuntime(); var testDTO = new TestDTO { StringVal = "Hello", IntVal = 4 }; var cts = new CancellationTokenSource(); var task = jsRuntime.InvokeAsync("unimportant", cts.Token); - var argsJson = JsonSerializer.Serialize(new object[] { jsRuntime.LastInvocationAsyncHandle, true, testDTO }, JsonSerializerOptionsProvider.Options); + var argsJson = JsonSerializer.Serialize(new object[] { jsRuntime.LastInvocationAsyncHandle, true, testDTO }, jsRuntime.JsonSerializerOptions); // Act cts.Cancel(); - DotNetDispatcher.EndInvokeJS(argsJson); + DotNetDispatcher.EndInvokeJS(jsRuntime, argsJson); // Assert Assert.True(task.IsCanceled); - }); + } [Fact] - public Task EndInvoke_WithNullError() => WithJSRuntime(async jsRuntime => + public async Task EndInvoke_WithNullError() { // Arrange + var jsRuntime = new TestJSRuntime(); var task = jsRuntime.InvokeAsync("unimportant"); - var argsJson = JsonSerializer.Serialize(new object[] { jsRuntime.LastInvocationAsyncHandle, false, null }, JsonSerializerOptionsProvider.Options); + var argsJson = JsonSerializer.Serialize(new object[] { jsRuntime.LastInvocationAsyncHandle, false, null }, jsRuntime.JsonSerializerOptions); // Act - DotNetDispatcher.EndInvokeJS(argsJson); + DotNetDispatcher.EndInvokeJS(jsRuntime, argsJson); // Assert var ex = await Assert.ThrowsAsync(async () => await task); Assert.Empty(ex.Message); - }); + } [Fact] - public Task CanInvokeInstanceMethodWithParams() => WithJSRuntime(jsRuntime => + public void CanInvokeInstanceMethodWithParams() { // Arrange: Track some instance plus another object we'll pass as a param + var jsRuntime = new TestJSRuntime(); var targetInstance = new SomePublicType(); var arg2 = new TestDTO { IntVal = 1234, StringVal = "My string" }; jsRuntime.Invoke("unimportant", @@ -330,38 +346,40 @@ namespace Microsoft.JSInterop.Infrastructure var argsJson = "[\"myvalue\",{\"__dotNetObject\":2}]"; // Act - var resultJson = DotNetDispatcher.Invoke(null, "InvokableInstanceMethod", 1, argsJson); + var resultJson = DotNetDispatcher.Invoke(jsRuntime, null, "InvokableInstanceMethod", 1, argsJson); // Assert Assert.Equal("[\"You passed myvalue\",{\"__dotNetObject\":3}]", resultJson); - var resultDto = ((DotNetObjectReference)jsRuntime.ObjectRefManager.FindDotNetObject(3)).Value; + var resultDto = ((DotNetObjectReference)jsRuntime.GetObjectReference(3)).Value; Assert.Equal(1235, resultDto.IntVal); Assert.Equal("MY STRING", resultDto.StringVal); - }); + } [Fact] - public Task CannotInvokeWithFewerNumberOfParameters() => WithJSRuntime(jsRuntime => + public void CannotInvokeWithFewerNumberOfParameters() { // Arrange + var jsRuntime = new TestJSRuntime(); var argsJson = JsonSerializer.Serialize(new object[] { new TestDTO { StringVal = "Another string", IntVal = 456 }, new[] { 100, 200 }, - }, JsonSerializerOptionsProvider.Options); + }, jsRuntime.JsonSerializerOptions); // Act/Assert var ex = Assert.Throws(() => { - DotNetDispatcher.Invoke(thisAssemblyName, "InvocableStaticWithParams", default, argsJson); + DotNetDispatcher.Invoke(jsRuntime, thisAssemblyName, "InvocableStaticWithParams", default, argsJson); }); Assert.Equal("The call to 'InvocableStaticWithParams' expects '3' parameters, but received '2'.", ex.Message); - }); + } [Fact] - public Task CannotInvokeWithMoreParameters() => WithJSRuntime(jsRuntime => + public void CannotInvokeWithMoreParameters() { // Arrange + var jsRuntime = new TestJSRuntime(); var objectRef = DotNetObjectReference.Create(new TestDTO { IntVal = 4 }); var argsJson = JsonSerializer.Serialize(new object[] { @@ -369,21 +387,22 @@ namespace Microsoft.JSInterop.Infrastructure new[] { 100, 200 }, objectRef, 7, - }, JsonSerializerOptionsProvider.Options); + }, jsRuntime.JsonSerializerOptions); // Act/Assert var ex = Assert.Throws(() => { - DotNetDispatcher.Invoke(thisAssemblyName, "InvocableStaticWithParams", default, argsJson); + DotNetDispatcher.Invoke(jsRuntime, thisAssemblyName, "InvocableStaticWithParams", default, argsJson); }); Assert.Equal("Unexpected JSON token Number. Ensure that the call to `InvocableStaticWithParams' is supplied with exactly '3' parameters.", ex.Message); - }); + } [Fact] - public Task CanInvokeAsyncMethod() => WithJSRuntime(async jsRuntime => + public async Task CanInvokeAsyncMethod() { // Arrange: Track some instance plus another object we'll pass as a param + var jsRuntime = new TestJSRuntime(); var targetInstance = new SomePublicType(); var arg2 = new TestDTO { IntVal = 1234, StringVal = "My string" }; var arg1Ref = DotNetObjectReference.Create(targetInstance); @@ -395,12 +414,12 @@ namespace Microsoft.JSInterop.Infrastructure { new TestDTO { IntVal = 1000, StringVal = "String via JSON" }, arg2Ref, - }, JsonSerializerOptionsProvider.Options); + }, jsRuntime.JsonSerializerOptions); // Act var callId = "123"; var resultTask = jsRuntime.NextInvocationTask; - DotNetDispatcher.BeginInvokeDotNet(callId, null, "InvokableAsyncMethod", 1, argsJson); + DotNetDispatcher.BeginInvokeDotNet(jsRuntime, callId, null, "InvokableAsyncMethod", 1, argsJson); await resultTask; // Assert: Correct completion information @@ -417,17 +436,18 @@ namespace Microsoft.JSInterop.Infrastructure var resultDto2 = resultDto2Ref.Value; Assert.Equal("MY STRING", resultDto2.StringVal); Assert.Equal(2468, resultDto2.IntVal); - }); + } [Fact] - public Task CanInvokeSyncThrowingMethod() => WithJSRuntime(async jsRuntime => + public async Task CanInvokeSyncThrowingMethod() { // Arrange + var jsRuntime = new TestJSRuntime(); // Act var callId = "123"; var resultTask = jsRuntime.NextInvocationTask; - DotNetDispatcher.BeginInvokeDotNet(callId, thisAssemblyName, nameof(ThrowingClass.ThrowingMethod), default, default); + DotNetDispatcher.BeginInvokeDotNet(jsRuntime, callId, thisAssemblyName, nameof(ThrowingClass.ThrowingMethod), default, default); await resultTask; // This won't throw, it sets properties on the jsRuntime. @@ -439,17 +459,18 @@ namespace Microsoft.JSInterop.Infrastructure // https://github.com/aspnet/AspNetCore/issues/8612 var exception = jsRuntime.LastCompletionResult is ExceptionDispatchInfo edi ? edi.SourceException.ToString() : null; Assert.Contains(nameof(ThrowingClass.ThrowingMethod), exception); - }); + } [Fact] - public Task CanInvokeAsyncThrowingMethod() => WithJSRuntime(async jsRuntime => + public async Task CanInvokeAsyncThrowingMethod() { // Arrange + var jsRuntime = new TestJSRuntime(); // Act var callId = "123"; var resultTask = jsRuntime.NextInvocationTask; - DotNetDispatcher.BeginInvokeDotNet(callId, thisAssemblyName, nameof(ThrowingClass.AsyncThrowingMethod), default, default); + DotNetDispatcher.BeginInvokeDotNet(jsRuntime, callId, thisAssemblyName, nameof(ThrowingClass.AsyncThrowingMethod), default, default); await resultTask; // This won't throw, it sets properties on the jsRuntime. @@ -461,15 +482,16 @@ namespace Microsoft.JSInterop.Infrastructure // https://github.com/aspnet/AspNetCore/issues/8612 var exception = jsRuntime.LastCompletionResult is ExceptionDispatchInfo edi ? edi.SourceException.ToString() : null; Assert.Contains(nameof(ThrowingClass.AsyncThrowingMethod), exception); - }); + } [Fact] - public Task BeginInvoke_ThrowsWithInvalidArgsJson_WithCallId() => WithJSRuntime(async jsRuntime => + public async Task BeginInvoke_ThrowsWithInvalidArgsJson_WithCallId() { // Arrange + var jsRuntime = new TestJSRuntime(); var callId = "123"; var resultTask = jsRuntime.NextInvocationTask; - DotNetDispatcher.BeginInvokeDotNet(callId, thisAssemblyName, "InvocableStaticWithParams", default, "not json"); + DotNetDispatcher.BeginInvokeDotNet(jsRuntime, callId, thisAssemblyName, "InvocableStaticWithParams", default, "not json"); await resultTask; // This won't throw, it sets properties on the jsRuntime. @@ -478,29 +500,30 @@ namespace Microsoft.JSInterop.Infrastructure Assert.False(jsRuntime.LastCompletionStatus); // Fails var result = Assert.IsType(jsRuntime.LastCompletionResult); Assert.Contains("JsonReaderException: '<' is an invalid start of a value.", result.SourceException.ToString()); - }); + } [Fact] - public Task BeginInvoke_ThrowsWithInvalid_DotNetObjectRef() => WithJSRuntime(jsRuntime => + public void BeginInvoke_ThrowsWithInvalid_DotNetObjectRef() { // Arrange + var jsRuntime = new TestJSRuntime(); var callId = "123"; var resultTask = jsRuntime.NextInvocationTask; - DotNetDispatcher.BeginInvokeDotNet(callId, null, "InvokableInstanceVoid", 1, null); + DotNetDispatcher.BeginInvokeDotNet(jsRuntime, callId, null, "InvokableInstanceVoid", 1, null); // Assert Assert.Equal(callId, jsRuntime.LastCompletionCallId); Assert.False(jsRuntime.LastCompletionStatus); // Fails var result = Assert.IsType(jsRuntime.LastCompletionResult); - Assert.StartsWith("System.ArgumentException: There is no tracked object with id '1'. Perhaps the DotNetObjectRef instance was already disposed.", result.SourceException.ToString()); - }); + Assert.StartsWith("System.ArgumentException: There is no tracked object with id '1'. Perhaps the DotNetObjectReference instance was already disposed.", result.SourceException.ToString()); + } [Theory] [InlineData("")] [InlineData("")] public void ParseArguments_ThrowsIfJsonIsInvalid(string arguments) { - Assert.ThrowsAny(() => DotNetDispatcher.ParseArguments("SomeMethod", arguments, new[] { typeof(string) })); + Assert.ThrowsAny(() => DotNetDispatcher.ParseArguments(new TestJSRuntime(), "SomeMethod", arguments, new[] { typeof(string) })); } [Theory] @@ -509,7 +532,7 @@ namespace Microsoft.JSInterop.Infrastructure public void ParseArguments_ThrowsIfTheArgsJsonIsNotArray(string arguments) { // Act & Assert - Assert.ThrowsAny(() => DotNetDispatcher.ParseArguments("SomeMethod", arguments, new[] { typeof(string) })); + Assert.ThrowsAny(() => DotNetDispatcher.ParseArguments(new TestJSRuntime(), "SomeMethod", arguments, new[] { typeof(string) })); } [Theory] @@ -518,7 +541,7 @@ namespace Microsoft.JSInterop.Infrastructure public void ParseArguments_ThrowsIfTheArgsJsonIsInvalidArray(string arguments) { // Act & Assert - Assert.ThrowsAny(() => DotNetDispatcher.ParseArguments("SomeMethod", arguments, new[] { typeof(string) })); + Assert.ThrowsAny(() => DotNetDispatcher.ParseArguments(new TestJSRuntime(), "SomeMethod", arguments, new[] { typeof(string) })); } [Fact] @@ -528,7 +551,7 @@ namespace Microsoft.JSInterop.Infrastructure var arguments = "[\"Hello\", 2]"; // Act - var result = DotNetDispatcher.ParseArguments("SomeMethod", arguments, new[] { typeof(string), typeof(int), }); + var result = DotNetDispatcher.ParseArguments(new TestJSRuntime(), "SomeMethod", arguments, new[] { typeof(string), typeof(int), }); // Assert Assert.Equal(new object[] { "Hello", 2 }, result); @@ -541,7 +564,7 @@ namespace Microsoft.JSInterop.Infrastructure var arguments = "[{\"IntVal\": 7}]"; // Act - var result = DotNetDispatcher.ParseArguments("SomeMethod", arguments, new[] { typeof(TestDTO), }); + var result = DotNetDispatcher.ParseArguments(new TestJSRuntime(), "SomeMethod", arguments, new[] { typeof(TestDTO), }); // Assert var value = Assert.IsType(Assert.Single(result)); @@ -556,7 +579,7 @@ namespace Microsoft.JSInterop.Infrastructure var arguments = "[4, null]"; // Act - var result = DotNetDispatcher.ParseArguments("SomeMethod", arguments, new[] { typeof(int), typeof(TestDTO), }); + var result = DotNetDispatcher.ParseArguments(new TestJSRuntime(), "SomeMethod", arguments, new[] { typeof(int), typeof(TestDTO), }); // Assert Assert.Collection( @@ -573,92 +596,72 @@ namespace Microsoft.JSInterop.Infrastructure var arguments = "[4, {\"__dotNetObject\": 7}]"; // Act - var ex = Assert.Throws(() => DotNetDispatcher.ParseArguments(method, arguments, new[] { typeof(int), typeof(TestDTO), })); + var ex = Assert.Throws(() => DotNetDispatcher.ParseArguments(new TestJSRuntime(), method, arguments, new[] { typeof(int), typeof(TestDTO), })); // Assert Assert.Equal($"In call to '{method}', parameter of type '{nameof(TestDTO)}' at index 2 must be declared as type 'DotNetObjectRef' to receive the incoming value.", ex.Message); } [Fact] - public void ParseEndInvokeArguments_ThrowsIfJsonIsEmptyString() + public void EndInvokeJS_ThrowsIfJsonIsEmptyString() { - Assert.ThrowsAny(() => DotNetDispatcher.ParseEndInvokeArguments(new TestJSRuntime(), "")); + Assert.ThrowsAny(() => DotNetDispatcher.EndInvokeJS(new TestJSRuntime(), "")); } [Fact] - public void ParseEndInvokeArguments_ThrowsIfJsonIsNotArray() + public void EndInvokeJS_ThrowsIfJsonIsNotArray() { - Assert.ThrowsAny(() => DotNetDispatcher.ParseEndInvokeArguments(new TestJSRuntime(), "{\"key\": \"value\"}")); + Assert.ThrowsAny(() => DotNetDispatcher.EndInvokeJS(new TestJSRuntime(), "{\"key\": \"value\"}")); } [Fact] - public void ParseEndInvokeArguments_ThrowsIfJsonArrayIsInComplete() + public void EndInvokeJS_ThrowsIfJsonArrayIsInComplete() { - Assert.ThrowsAny(() => DotNetDispatcher.ParseEndInvokeArguments(new TestJSRuntime(), "[7, false")); + Assert.ThrowsAny(() => DotNetDispatcher.EndInvokeJS(new TestJSRuntime(), "[7, false")); } [Fact] - public void ParseEndInvokeArguments_ThrowsIfJsonArrayHasMoreThan3Arguments() + public void EndInvokeJS_ThrowsIfJsonArrayHasMoreThan3Arguments() { - Assert.ThrowsAny(() => DotNetDispatcher.ParseEndInvokeArguments(new TestJSRuntime(), "[7, false, \"Hello\", 5]")); + Assert.ThrowsAny(() => DotNetDispatcher.EndInvokeJS(new TestJSRuntime(), "[7, false, \"Hello\", 5]")); } [Fact] - public void ParseEndInvokeArguments_Works() + public void EndInvokeJS_Works() { var jsRuntime = new TestJSRuntime(); var task = jsRuntime.InvokeAsync("somemethod"); - DotNetDispatcher.ParseEndInvokeArguments(jsRuntime, $"[{jsRuntime.LastInvocationAsyncHandle}, true, {{\"intVal\": 7}}]"); + DotNetDispatcher.EndInvokeJS(jsRuntime, $"[{jsRuntime.LastInvocationAsyncHandle}, true, {{\"intVal\": 7}}]"); Assert.True(task.IsCompletedSuccessfully); Assert.Equal(7, task.Result.IntVal); } [Fact] - public void ParseEndInvokeArguments_WithArrayValue() + public void EndInvokeJS_WithArrayValue() { var jsRuntime = new TestJSRuntime(); var task = jsRuntime.InvokeAsync("somemethod"); - DotNetDispatcher.ParseEndInvokeArguments(jsRuntime, $"[{jsRuntime.LastInvocationAsyncHandle}, true, [1, 2, 3]]"); + DotNetDispatcher.EndInvokeJS(jsRuntime, $"[{jsRuntime.LastInvocationAsyncHandle}, true, [1, 2, 3]]"); Assert.True(task.IsCompletedSuccessfully); Assert.Equal(new[] { 1, 2, 3 }, task.Result); } [Fact] - public void ParseEndInvokeArguments_WithNullValue() + public void EndInvokeJS_WithNullValue() { var jsRuntime = new TestJSRuntime(); var task = jsRuntime.InvokeAsync("somemethod"); - DotNetDispatcher.ParseEndInvokeArguments(jsRuntime, $"[{jsRuntime.LastInvocationAsyncHandle}, true, null]"); + DotNetDispatcher.EndInvokeJS(jsRuntime, $"[{jsRuntime.LastInvocationAsyncHandle}, true, null]"); Assert.True(task.IsCompletedSuccessfully); Assert.Null(task.Result); } - Task WithJSRuntime(Action testCode) - { - return WithJSRuntime(jsRuntime => - { - testCode(jsRuntime); - return Task.CompletedTask; - }); - } - - async Task WithJSRuntime(Func testCode) - { - // Since the tests rely on the asynclocal JSRuntime.Current, ensure we - // are on a distinct async context with a non-null JSRuntime.Current - await Task.Yield(); - - var runtime = new TestJSRuntime(); - JSRuntime.SetCurrentJSRuntime(runtime); - await testCode(runtime); - } - internal class SomeInteralType { [JSInvokable("MethodOnInternalType")] public void MyMethod() { } diff --git a/src/JSInterop/Microsoft.JSInterop/test/Infrastructure/DotNetObjectReferenceJsonConverterTest.cs b/src/JSInterop/Microsoft.JSInterop/test/Infrastructure/DotNetObjectReferenceJsonConverterTest.cs index 541ad2b025..8d055aea2c 100644 --- a/src/JSInterop/Microsoft.JSInterop/test/Infrastructure/DotNetObjectReferenceJsonConverterTest.cs +++ b/src/JSInterop/Microsoft.JSInterop/test/Infrastructure/DotNetObjectReferenceJsonConverterTest.cs @@ -2,91 +2,93 @@ // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System.Text.Json; -using System.Threading.Tasks; using Xunit; -using static Microsoft.JSInterop.TestJSRuntime; namespace Microsoft.JSInterop.Infrastructure { public class DotNetObjectReferenceJsonConverterTest { + private readonly JSRuntime JSRuntime = new TestJSRuntime(); + private JsonSerializerOptions JsonSerializerOptions => JSRuntime.JsonSerializerOptions; + [Fact] - public Task Read_Throws_IfJsonIsMissingDotNetObjectProperty() => WithJSRuntime(_ => + public void Read_Throws_IfJsonIsMissingDotNetObjectProperty() { // Arrange + var jsRuntime = new TestJSRuntime(); var dotNetObjectRef = DotNetObjectReference.Create(new TestModel()); var json = "{}"; // Act & Assert - var ex = Assert.Throws(() => JsonSerializer.Deserialize>(json)); + var ex = Assert.Throws(() => JsonSerializer.Deserialize>(json, JsonSerializerOptions)); Assert.Equal("Required property __dotNetObject not found.", ex.Message); - }); + } [Fact] - public Task Read_Throws_IfJsonContainsUnknownContent() => WithJSRuntime(_ => + public void Read_Throws_IfJsonContainsUnknownContent() { // Arrange + var jsRuntime = new TestJSRuntime(); var dotNetObjectRef = DotNetObjectReference.Create(new TestModel()); var json = "{\"foo\":2}"; // Act & Assert - var ex = Assert.Throws(() => JsonSerializer.Deserialize>(json)); + var ex = Assert.Throws(() => JsonSerializer.Deserialize>(json, JsonSerializerOptions)); Assert.Equal("Unexcepted JSON property foo.", ex.Message); - }); + } [Fact] - public Task Read_Throws_IfJsonIsIncomplete() => WithJSRuntime(_ => + public void Read_Throws_IfJsonIsIncomplete() { // Arrange var input = new TestModel(); var dotNetObjectRef = DotNetObjectReference.Create(input); - var objectId = dotNetObjectRef.ObjectId; + var objectId = JSRuntime.TrackObjectReference(dotNetObjectRef); var json = $"{{\"__dotNetObject\":{objectId}"; // Act & Assert - var ex = Record.Exception(() => JsonSerializer.Deserialize>(json)); + var ex = Record.Exception(() => JsonSerializer.Deserialize>(json, JsonSerializerOptions)); Assert.IsAssignableFrom(ex); - }); + } [Fact] - public Task Read_Throws_IfDotNetObjectIdAppearsMultipleTimes() => WithJSRuntime(_ => + public void Read_Throws_IfDotNetObjectIdAppearsMultipleTimes() { // Arrange var input = new TestModel(); var dotNetObjectRef = DotNetObjectReference.Create(input); - var objectId = dotNetObjectRef.ObjectId; + var objectId = JSRuntime.TrackObjectReference(dotNetObjectRef); var json = $"{{\"__dotNetObject\":{objectId},\"__dotNetObject\":{objectId}}}"; // Act & Assert - var ex = Record.Exception(() => JsonSerializer.Deserialize>(json)); + var ex = Record.Exception(() => JsonSerializer.Deserialize>(json, JsonSerializerOptions)); Assert.IsAssignableFrom(ex); - }); + } [Fact] - public Task Read_ReadsJson() => WithJSRuntime(_ => + public void Read_ReadsJson() { // Arrange var input = new TestModel(); var dotNetObjectRef = DotNetObjectReference.Create(input); - var objectId = dotNetObjectRef.ObjectId; + var objectId = JSRuntime.TrackObjectReference(dotNetObjectRef); var json = $"{{\"__dotNetObject\":{objectId}}}"; // Act - var deserialized = JsonSerializer.Deserialize>(json); + var deserialized = JsonSerializer.Deserialize>(json, JsonSerializerOptions); // Assert Assert.Same(input, deserialized.Value); Assert.Equal(objectId, deserialized.ObjectId); - }); - + } [Fact] - public Task Read_ReturnsTheCorrectInstance() => WithJSRuntime(_ => + public void Read_ReturnsTheCorrectInstance() { // Arrange // Track a few instances and verify that the deserialized value returns the correct value. @@ -95,23 +97,23 @@ namespace Microsoft.JSInterop.Infrastructure var ref1 = DotNetObjectReference.Create(instance1); var ref2 = DotNetObjectReference.Create(instance2); - var json = $"[{{\"__dotNetObject\":{ref2.ObjectId}}},{{\"__dotNetObject\":{ref1.ObjectId}}}]"; + var json = $"[{{\"__dotNetObject\":{JSRuntime.TrackObjectReference(ref1)}}},{{\"__dotNetObject\":{JSRuntime.TrackObjectReference(ref2)}}}]"; // Act - var deserialized = JsonSerializer.Deserialize[]>(json); + var deserialized = JsonSerializer.Deserialize[]>(json, JsonSerializerOptions); // Assert - Assert.Same(instance2, deserialized[0].Value); - Assert.Same(instance1, deserialized[1].Value); - }); + Assert.Same(instance1, deserialized[0].Value); + Assert.Same(instance2, deserialized[1].Value); + } [Fact] - public Task Read_ReadsJson_WithFormatting() => WithJSRuntime(_ => + public void Read_ReadsJson_WithFormatting() { // Arrange var input = new TestModel(); var dotNetObjectRef = DotNetObjectReference.Create(input); - var objectId = dotNetObjectRef.ObjectId; + var objectId = JSRuntime.TrackObjectReference(dotNetObjectRef); var json = @$"{{ @@ -119,27 +121,27 @@ namespace Microsoft.JSInterop.Infrastructure }}"; // Act - var deserialized = JsonSerializer.Deserialize>(json); + var deserialized = JsonSerializer.Deserialize>(json, JsonSerializerOptions); // Assert Assert.Same(input, deserialized.Value); Assert.Equal(objectId, deserialized.ObjectId); - }); + } [Fact] - public Task WriteJsonTwice_KeepsObjectId() => WithJSRuntime(_ => + public void WriteJsonTwice_KeepsObjectId() { // Arrange var dotNetObjectRef = DotNetObjectReference.Create(new TestModel()); // Act - var json1 = JsonSerializer.Serialize(dotNetObjectRef); - var json2 = JsonSerializer.Serialize(dotNetObjectRef); + var json1 = JsonSerializer.Serialize(dotNetObjectRef, JsonSerializerOptions); + var json2 = JsonSerializer.Serialize(dotNetObjectRef, JsonSerializerOptions); // Assert Assert.Equal($"{{\"__dotNetObject\":{dotNetObjectRef.ObjectId}}}", json1); Assert.Equal(json1, json2); - }); + } private class TestModel { diff --git a/src/JSInterop/Microsoft.JSInterop/test/JSInProcessRuntimeTest.cs b/src/JSInterop/Microsoft.JSInterop/test/JSInProcessRuntimeTest.cs index 4054101258..a1caff595b 100644 --- a/src/JSInterop/Microsoft.JSInterop/test/JSInProcessRuntimeTest.cs +++ b/src/JSInterop/Microsoft.JSInterop/test/JSInProcessRuntimeTest.cs @@ -18,7 +18,6 @@ namespace Microsoft.JSInterop { NextResultJson = "{\"intValue\":123,\"stringValue\":\"Hello\"}" }; - JSRuntime.SetCurrentJSRuntime(runtime); // Act var syncResult = runtime.Invoke("test identifier 1", "arg1", 123, true); @@ -36,7 +35,6 @@ namespace Microsoft.JSInterop { // Arrange var runtime = new TestJSInProcessRuntime { NextResultJson = null }; - JSRuntime.SetCurrentJSRuntime(runtime); var obj1 = new object(); var obj2 = new object(); var obj3 = new object(); @@ -60,9 +58,9 @@ namespace Microsoft.JSInterop Assert.Equal("[{\"__dotNetObject\":1},{\"obj2\":{\"__dotNetObject\":2},\"obj3\":{\"__dotNetObject\":3}}]", call.ArgsJson); // Assert: Objects were tracked - Assert.Same(obj1, runtime.ObjectRefManager.FindDotNetObject(1).Value); - Assert.Same(obj2, runtime.ObjectRefManager.FindDotNetObject(2).Value); - Assert.Same(obj3, runtime.ObjectRefManager.FindDotNetObject(3).Value); + Assert.Same(obj1, runtime.GetObjectReference(1).Value); + Assert.Same(obj2, runtime.GetObjectReference(2).Value); + Assert.Same(obj3, runtime.GetObjectReference(3).Value); } [Fact] @@ -73,7 +71,6 @@ namespace Microsoft.JSInterop { NextResultJson = "[{\"__dotNetObject\":2},{\"__dotNetObject\":1}]" }; - JSRuntime.SetCurrentJSRuntime(runtime); var obj1 = new object(); var obj2 = new object(); diff --git a/src/JSInterop/Microsoft.JSInterop/test/JSRuntimeTest.cs b/src/JSInterop/Microsoft.JSInterop/test/JSRuntimeTest.cs index 4e65ddeb0f..b102ecc0b5 100644 --- a/src/JSInterop/Microsoft.JSInterop/test/JSRuntimeTest.cs +++ b/src/JSInterop/Microsoft.JSInterop/test/JSRuntimeTest.cs @@ -14,23 +14,6 @@ namespace Microsoft.JSInterop { public class JSRuntimeTest { - #region this will be removed eventually - [Fact] - public async Task CanHaveDistinctJSRuntimeInstancesInEachAsyncContext() - { - var tasks = Enumerable.Range(0, 20).Select(async _ => - { - var jsRuntime = new TestJSRuntime(); - JSRuntime.SetCurrentJSRuntime(jsRuntime); - await Task.Delay(50).ConfigureAwait(false); - Assert.Same(jsRuntime, JSRuntime.Current); - }); - - await Task.WhenAll(tasks); - Assert.Null(JSRuntime.Current); - } - #endregion - [Fact] public void DispatchesAsyncCallsWithDistinctAsyncHandles() { @@ -274,7 +257,6 @@ namespace Microsoft.JSInterop { // Arrange var runtime = new TestJSRuntime(); - JSRuntime.SetCurrentJSRuntime(runtime); var obj1 = new object(); var obj2 = new object(); var obj3 = new object(); @@ -296,15 +278,15 @@ namespace Microsoft.JSInterop // Assert: Serialized as expected var call = runtime.BeginInvokeCalls.Single(); Assert.Equal("test identifier", call.Identifier); - Assert.Equal("[{\"__dotNetObject\":1},{\"obj2\":{\"__dotNetObject\":3},\"obj3\":{\"__dotNetObject\":4},\"obj1SameRef\":{\"__dotNetObject\":1},\"obj1DifferentRef\":{\"__dotNetObject\":2}}]", call.ArgsJson); + Assert.Equal("[{\"__dotNetObject\":1},{\"obj2\":{\"__dotNetObject\":2},\"obj3\":{\"__dotNetObject\":3},\"obj1SameRef\":{\"__dotNetObject\":1},\"obj1DifferentRef\":{\"__dotNetObject\":4}}]", call.ArgsJson); // Assert: Objects were tracked - Assert.Same(obj1Ref, runtime.ObjectRefManager.FindDotNetObject(1)); + Assert.Same(obj1Ref, runtime.GetObjectReference(1)); Assert.Same(obj1, obj1Ref.Value); - Assert.NotSame(obj1Ref, runtime.ObjectRefManager.FindDotNetObject(2)); - Assert.Same(obj1, runtime.ObjectRefManager.FindDotNetObject(2).Value); - Assert.Same(obj2, runtime.ObjectRefManager.FindDotNetObject(3).Value); - Assert.Same(obj3, runtime.ObjectRefManager.FindDotNetObject(4).Value); + Assert.NotSame(obj1Ref, runtime.GetObjectReference(2)); + Assert.Same(obj2, runtime.GetObjectReference(2).Value); + Assert.Same(obj3, runtime.GetObjectReference(3).Value); + Assert.Same(obj1, runtime.GetObjectReference(4).Value); } [Fact] diff --git a/src/JSInterop/Microsoft.JSInterop/test/TestJSRuntime.cs b/src/JSInterop/Microsoft.JSInterop/test/TestJSRuntime.cs index 48782fc4df..740f02b8da 100644 --- a/src/JSInterop/Microsoft.JSInterop/test/TestJSRuntime.cs +++ b/src/JSInterop/Microsoft.JSInterop/test/TestJSRuntime.cs @@ -2,7 +2,6 @@ // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; -using System.Threading.Tasks; namespace Microsoft.JSInterop { @@ -17,16 +16,5 @@ namespace Microsoft.JSInterop { throw new NotImplementedException(); } - - public static async Task WithJSRuntime(Action testCode) - { - // Since the tests rely on the asynclocal JSRuntime.Current, ensure we - // are on a distinct async context with a non-null JSRuntime.Current - await Task.Yield(); - - var runtime = new TestJSRuntime(); - JSRuntime.SetCurrentJSRuntime(runtime); - testCode(runtime); - } } } diff --git a/src/JSInterop/Mono.WebAssembly.Interop/ref/Mono.WebAssembly.Interop.netstandard2.0.cs b/src/JSInterop/Mono.WebAssembly.Interop/ref/Mono.WebAssembly.Interop.netstandard2.0.cs index 2e4defd1b7..0996795ca3 100644 --- a/src/JSInterop/Mono.WebAssembly.Interop/ref/Mono.WebAssembly.Interop.netstandard2.0.cs +++ b/src/JSInterop/Mono.WebAssembly.Interop/ref/Mono.WebAssembly.Interop.netstandard2.0.cs @@ -8,6 +8,7 @@ namespace Mono.WebAssembly.Interop public MonoWebAssemblyJSRuntime() { } protected override void BeginInvokeJS(long asyncHandle, string identifier, string argsJson) { } protected override void EndInvokeDotNet(string callId, bool success, object resultOrError, string assemblyName, string methodIdentifier, long dotNetObjectId) { } + protected static void Initialize(Mono.WebAssembly.Interop.MonoWebAssemblyJSRuntime jsRuntime) { } protected override string InvokeJS(string identifier, string argsJson) { throw null; } public TRes InvokeUnmarshalled(string identifier) { throw null; } public TRes InvokeUnmarshalled(string identifier, T0 arg0) { throw null; } diff --git a/src/JSInterop/Mono.WebAssembly.Interop/src/MonoWebAssemblyJSRuntime.cs b/src/JSInterop/Mono.WebAssembly.Interop/src/MonoWebAssemblyJSRuntime.cs index 0e292a3e3c..b6b01d754d 100644 --- a/src/JSInterop/Mono.WebAssembly.Interop/src/MonoWebAssemblyJSRuntime.cs +++ b/src/JSInterop/Mono.WebAssembly.Interop/src/MonoWebAssemblyJSRuntime.cs @@ -16,6 +16,25 @@ namespace Mono.WebAssembly.Interop /// public class MonoWebAssemblyJSRuntime : JSInProcessRuntime { + /// + /// Gets the used to perform operations using . + /// + private static MonoWebAssemblyJSRuntime Instance { get; set; } + + /// + /// Initializes the to be used to perform operations using . + /// + /// The instance. + protected static void Initialize(MonoWebAssemblyJSRuntime jsRuntime) + { + if (Instance != null) + { + throw new InvalidOperationException("MonoWebAssemblyJSRuntime has already been initialized."); + } + + Instance = jsRuntime ?? throw new ArgumentNullException(nameof(jsRuntime)); + } + /// protected override string InvokeJS(string identifier, string argsJson) { @@ -34,11 +53,11 @@ namespace Mono.WebAssembly.Interop // Invoked via Mono's JS interop mechanism (invoke_method) private static string InvokeDotNet(string assemblyName, string methodIdentifier, string dotNetObjectId, string argsJson) - => DotNetDispatcher.Invoke(assemblyName, methodIdentifier, dotNetObjectId == null ? default : long.Parse(dotNetObjectId), argsJson); + => DotNetDispatcher.Invoke(Instance, assemblyName, methodIdentifier, dotNetObjectId == null ? default : long.Parse(dotNetObjectId), argsJson); // Invoked via Mono's JS interop mechanism (invoke_method) private static void EndInvokeJS(string argsJson) - => DotNetDispatcher.EndInvokeJS(argsJson); + => DotNetDispatcher.EndInvokeJS(Instance, argsJson); // Invoked via Mono's JS interop mechanism (invoke_method) private static void BeginInvokeDotNet(string callId, string assemblyNameOrDotNetObjectId, string methodIdentifier, string argsJson) @@ -59,7 +78,7 @@ namespace Mono.WebAssembly.Interop assemblyName = assemblyNameOrDotNetObjectId; } - DotNetDispatcher.BeginInvokeDotNet(callId, assemblyName, methodIdentifier, dotNetObjectId, argsJson); + DotNetDispatcher.BeginInvokeDotNet(Instance, callId, assemblyName, methodIdentifier, dotNetObjectId, argsJson); } protected override void EndInvokeDotNet( @@ -84,7 +103,7 @@ namespace Mono.WebAssembly.Interop // We pass 0 as the async handle because we don't want the JS-side code to // send back any notification (we're just providing a result for an existing async call) - var args = JsonSerializer.Serialize(new[] { callId, success, resultOrError }, new JsonSerializerOptions { PropertyNamingPolicy = JsonNamingPolicy.CamelCase }); + var args = JsonSerializer.Serialize(new[] { callId, success, resultOrError }, JsonSerializerOptions); BeginInvokeJS(0, "DotNet.jsCallDispatcher.endInvokeDotNetFromJS", args); } From 591d6c13ec665d9ac0ab71056c04841530804841 Mon Sep 17 00:00:00 2001 From: Brennan Date: Fri, 16 Aug 2019 13:47:32 -0700 Subject: [PATCH 0165/1101] Add support for netcoreapp5.0 to micro benchmarks (dotnet/extensions#2190) \n\nCommit migrated from https://github.com/dotnet/extensions/commit/72bcb43aee992f77287165de5b6140553c5750eb --- src/Shared/BenchmarkRunner/DefaultCoreConfig.cs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/Shared/BenchmarkRunner/DefaultCoreConfig.cs b/src/Shared/BenchmarkRunner/DefaultCoreConfig.cs index a61833ab26..d24bb439a1 100644 --- a/src/Shared/BenchmarkRunner/DefaultCoreConfig.cs +++ b/src/Shared/BenchmarkRunner/DefaultCoreConfig.cs @@ -30,8 +30,12 @@ namespace BenchmarkDotNet.Attributes Add(Job.Core #if NETCOREAPP2_1 .With(CsProjCoreToolchain.From(NetCoreAppSettings.NetCoreApp21)) -#else +#elif NETCOREAPP3_0 .With(CsProjCoreToolchain.From(new NetCoreAppSettings("netcoreapp3.0", null, ".NET Core 3.0"))) +#elif NETCOREAPP5_0 + .With(CsProjCoreToolchain.From(new NetCoreAppSettings("netcoreapp5.0", null, ".NET Core 5.0"))) +#else +#error Target frameworks need to be updated. #endif .With(new GcMode { Server = true }) .With(RunStrategy.Throughput)); From d46d569b8122ec3337fcae6a6814a6fb316bc45c Mon Sep 17 00:00:00 2001 From: Pranav K Date: Fri, 16 Aug 2019 16:19:23 -0700 Subject: [PATCH 0166/1101] Simplify JSRuntime method signature (dotnet/extensions#2188) \n\nCommit migrated from https://github.com/dotnet/extensions/commit/9c392a92efa88707023d8f0a47681aee217a9c56 --- .../ref/Microsoft.JSInterop.netcoreapp3.0.cs | 29 ++++++- .../ref/Microsoft.JSInterop.netstandard2.0.cs | 29 ++++++- .../src/Infrastructure/DotNetDispatcher.cs | 46 +++++----- .../Infrastructure/DotNetInvocationInfo.cs | 48 +++++++++++ .../Infrastructure/DotNetInvocationResult.cs | 55 ++++++++++++ .../Microsoft.JSInterop/src/JSRuntime.cs | 16 +--- .../Infrastructure/DotNetDispatcherTest.cs | 86 ++++++++----------- .../test/JSInProcessRuntimeTest.cs | 5 +- .../Microsoft.JSInterop/test/JSRuntimeTest.cs | 24 +++--- .../Microsoft.JSInterop/test/TestJSRuntime.cs | 3 +- ...Mono.WebAssembly.Interop.netstandard2.0.cs | 2 +- .../src/MonoWebAssemblyJSRuntime.cs | 28 ++---- 12 files changed, 249 insertions(+), 122 deletions(-) create mode 100644 src/JSInterop/Microsoft.JSInterop/src/Infrastructure/DotNetInvocationInfo.cs create mode 100644 src/JSInterop/Microsoft.JSInterop/src/Infrastructure/DotNetInvocationResult.cs diff --git a/src/JSInterop/Microsoft.JSInterop/ref/Microsoft.JSInterop.netcoreapp3.0.cs b/src/JSInterop/Microsoft.JSInterop/ref/Microsoft.JSInterop.netcoreapp3.0.cs index 953f8b0329..2d8c51caaf 100644 --- a/src/JSInterop/Microsoft.JSInterop/ref/Microsoft.JSInterop.netcoreapp3.0.cs +++ b/src/JSInterop/Microsoft.JSInterop/ref/Microsoft.JSInterop.netcoreapp3.0.cs @@ -50,7 +50,7 @@ namespace Microsoft.JSInterop protected System.TimeSpan? DefaultAsyncTimeout { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } protected internal System.Text.Json.JsonSerializerOptions JsonSerializerOptions { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } } protected abstract void BeginInvokeJS(long taskId, string identifier, string argsJson); - protected internal abstract void EndInvokeDotNet(string callId, bool success, object resultOrError, string assemblyName, string methodIdentifier, long dotNetObjectId); + protected internal abstract void EndInvokeDotNet(Microsoft.JSInterop.Infrastructure.DotNetInvocationInfo invocationInfo, in Microsoft.JSInterop.Infrastructure.DotNetInvocationResult invocationResult); public System.Threading.Tasks.ValueTask InvokeAsync(string identifier, object[] args) { throw null; } public System.Threading.Tasks.ValueTask InvokeAsync(string identifier, System.Threading.CancellationToken cancellationToken, object[] args) { throw null; } } @@ -72,8 +72,31 @@ namespace Microsoft.JSInterop.Infrastructure { public static partial class DotNetDispatcher { - public static void BeginInvokeDotNet(Microsoft.JSInterop.JSRuntime jsRuntime, string callId, string assemblyName, string methodIdentifier, long dotNetObjectId, string argsJson) { } + public static void BeginInvokeDotNet(Microsoft.JSInterop.JSRuntime jsRuntime, Microsoft.JSInterop.Infrastructure.DotNetInvocationInfo invocationInfo, string argsJson) { } public static void EndInvokeJS(Microsoft.JSInterop.JSRuntime jsRuntime, string arguments) { } - public static string Invoke(Microsoft.JSInterop.JSRuntime jsRuntime, string assemblyName, string methodIdentifier, long dotNetObjectId, string argsJson) { throw null; } + public static string Invoke(Microsoft.JSInterop.JSRuntime jsRuntime, in Microsoft.JSInterop.Infrastructure.DotNetInvocationInfo invocationInfo, string argsJson) { throw null; } + } + [System.Runtime.InteropServices.StructLayoutAttribute(System.Runtime.InteropServices.LayoutKind.Sequential)] + public readonly partial struct DotNetInvocationInfo + { + private readonly object _dummy; + private readonly int _dummyPrimitive; + public DotNetInvocationInfo(string assemblyName, string methodIdentifier, long dotNetObjectId, string callId) { throw null; } + public string AssemblyName { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } } + public string CallId { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } } + public long DotNetObjectId { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } } + public string MethodIdentifier { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } } + } + [System.Runtime.InteropServices.StructLayoutAttribute(System.Runtime.InteropServices.LayoutKind.Sequential)] + public readonly partial struct DotNetInvocationResult + { + private readonly object _dummy; + private readonly int _dummyPrimitive; + public DotNetInvocationResult(System.Exception exception, string errorKind) { throw null; } + public DotNetInvocationResult(object result) { throw null; } + public string ErrorKind { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } } + public System.Exception Exception { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } } + public object Result { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } } + public bool Success { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } } } } diff --git a/src/JSInterop/Microsoft.JSInterop/ref/Microsoft.JSInterop.netstandard2.0.cs b/src/JSInterop/Microsoft.JSInterop/ref/Microsoft.JSInterop.netstandard2.0.cs index 953f8b0329..2d8c51caaf 100644 --- a/src/JSInterop/Microsoft.JSInterop/ref/Microsoft.JSInterop.netstandard2.0.cs +++ b/src/JSInterop/Microsoft.JSInterop/ref/Microsoft.JSInterop.netstandard2.0.cs @@ -50,7 +50,7 @@ namespace Microsoft.JSInterop protected System.TimeSpan? DefaultAsyncTimeout { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } protected internal System.Text.Json.JsonSerializerOptions JsonSerializerOptions { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } } protected abstract void BeginInvokeJS(long taskId, string identifier, string argsJson); - protected internal abstract void EndInvokeDotNet(string callId, bool success, object resultOrError, string assemblyName, string methodIdentifier, long dotNetObjectId); + protected internal abstract void EndInvokeDotNet(Microsoft.JSInterop.Infrastructure.DotNetInvocationInfo invocationInfo, in Microsoft.JSInterop.Infrastructure.DotNetInvocationResult invocationResult); public System.Threading.Tasks.ValueTask InvokeAsync(string identifier, object[] args) { throw null; } public System.Threading.Tasks.ValueTask InvokeAsync(string identifier, System.Threading.CancellationToken cancellationToken, object[] args) { throw null; } } @@ -72,8 +72,31 @@ namespace Microsoft.JSInterop.Infrastructure { public static partial class DotNetDispatcher { - public static void BeginInvokeDotNet(Microsoft.JSInterop.JSRuntime jsRuntime, string callId, string assemblyName, string methodIdentifier, long dotNetObjectId, string argsJson) { } + public static void BeginInvokeDotNet(Microsoft.JSInterop.JSRuntime jsRuntime, Microsoft.JSInterop.Infrastructure.DotNetInvocationInfo invocationInfo, string argsJson) { } public static void EndInvokeJS(Microsoft.JSInterop.JSRuntime jsRuntime, string arguments) { } - public static string Invoke(Microsoft.JSInterop.JSRuntime jsRuntime, string assemblyName, string methodIdentifier, long dotNetObjectId, string argsJson) { throw null; } + public static string Invoke(Microsoft.JSInterop.JSRuntime jsRuntime, in Microsoft.JSInterop.Infrastructure.DotNetInvocationInfo invocationInfo, string argsJson) { throw null; } + } + [System.Runtime.InteropServices.StructLayoutAttribute(System.Runtime.InteropServices.LayoutKind.Sequential)] + public readonly partial struct DotNetInvocationInfo + { + private readonly object _dummy; + private readonly int _dummyPrimitive; + public DotNetInvocationInfo(string assemblyName, string methodIdentifier, long dotNetObjectId, string callId) { throw null; } + public string AssemblyName { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } } + public string CallId { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } } + public long DotNetObjectId { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } } + public string MethodIdentifier { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } } + } + [System.Runtime.InteropServices.StructLayoutAttribute(System.Runtime.InteropServices.LayoutKind.Sequential)] + public readonly partial struct DotNetInvocationResult + { + private readonly object _dummy; + private readonly int _dummyPrimitive; + public DotNetInvocationResult(System.Exception exception, string errorKind) { throw null; } + public DotNetInvocationResult(object result) { throw null; } + public string ErrorKind { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } } + public System.Exception Exception { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } } + public object Result { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } } + public bool Success { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } } } } diff --git a/src/JSInterop/Microsoft.JSInterop/src/Infrastructure/DotNetDispatcher.cs b/src/JSInterop/Microsoft.JSInterop/src/Infrastructure/DotNetDispatcher.cs index 92afc6278d..6a3a4f8d5f 100644 --- a/src/JSInterop/Microsoft.JSInterop/src/Infrastructure/DotNetDispatcher.cs +++ b/src/JSInterop/Microsoft.JSInterop/src/Infrastructure/DotNetDispatcher.cs @@ -28,12 +28,10 @@ namespace Microsoft.JSInterop.Infrastructure /// Receives a call from JS to .NET, locating and invoking the specified method. /// /// The . - /// The assembly containing the method to be invoked. - /// The identifier of the method to be invoked. The method must be annotated with a matching this identifier string. - /// For instance method calls, identifies the target object. + /// The . /// A JSON representation of the parameters. /// A JSON representation of the return value, or null. - public static string Invoke(JSRuntime jsRuntime, string assemblyName, string methodIdentifier, long dotNetObjectId, string argsJson) + public static string Invoke(JSRuntime jsRuntime, in DotNetInvocationInfo invocationInfo, string argsJson) { // This method doesn't need [JSInvokable] because the platform is responsible for having // some way to dispatch calls here. The logic inside here is the thing that checks whether @@ -41,12 +39,12 @@ namespace Microsoft.JSInterop.Infrastructure // because there would be nobody to police that. This method *is* the police. IDotNetObjectReference targetInstance = default; - if (dotNetObjectId != default) + if (invocationInfo.DotNetObjectId != default) { - targetInstance = jsRuntime.GetObjectReference(dotNetObjectId); + targetInstance = jsRuntime.GetObjectReference(invocationInfo.DotNetObjectId); } - var syncResult = InvokeSynchronously(jsRuntime, assemblyName, methodIdentifier, targetInstance, argsJson); + var syncResult = InvokeSynchronously(jsRuntime, invocationInfo, targetInstance, argsJson); if (syncResult == null) { return null; @@ -59,13 +57,10 @@ namespace Microsoft.JSInterop.Infrastructure /// Receives a call from JS to .NET, locating and invoking the specified method asynchronously. /// /// The . - /// A value identifying the asynchronous call that should be passed back with the result, or null if no result notification is required. - /// The assembly containing the method to be invoked. - /// The identifier of the method to be invoked. The method must be annotated with a matching this identifier string. - /// For instance method calls, identifies the target object. + /// The . /// A JSON representation of the parameters. /// A JSON representation of the return value, or null. - public static void BeginInvokeDotNet(JSRuntime jsRuntime, string callId, string assemblyName, string methodIdentifier, long dotNetObjectId, string argsJson) + public static void BeginInvokeDotNet(JSRuntime jsRuntime, DotNetInvocationInfo invocationInfo, string argsJson) { // This method doesn't need [JSInvokable] because the platform is responsible for having // some way to dispatch calls here. The logic inside here is the thing that checks whether @@ -75,18 +70,19 @@ namespace Microsoft.JSInterop.Infrastructure // Using ExceptionDispatchInfo here throughout because we want to always preserve // original stack traces. + var callId = invocationInfo.CallId; + object syncResult = null; ExceptionDispatchInfo syncException = null; IDotNetObjectReference targetInstance = null; - try { - if (dotNetObjectId != default) + if (invocationInfo.DotNetObjectId != default) { - targetInstance = jsRuntime.GetObjectReference(dotNetObjectId); + targetInstance = jsRuntime.GetObjectReference(invocationInfo.DotNetObjectId); } - syncResult = InvokeSynchronously(jsRuntime, assemblyName, methodIdentifier, targetInstance, argsJson); + syncResult = InvokeSynchronously(jsRuntime, invocationInfo, targetInstance, argsJson); } catch (Exception ex) { @@ -101,7 +97,7 @@ namespace Microsoft.JSInterop.Infrastructure else if (syncException != null) { // Threw synchronously, let's respond. - jsRuntime.EndInvokeDotNet(callId, false, syncException, assemblyName, methodIdentifier, dotNetObjectId); + jsRuntime.EndInvokeDotNet(invocationInfo, new DotNetInvocationResult(syncException.SourceException, "InvocationFailure")); } else if (syncResult is Task task) { @@ -111,23 +107,27 @@ namespace Microsoft.JSInterop.Infrastructure { if (t.Exception != null) { - var exception = t.Exception.GetBaseException(); - - jsRuntime.EndInvokeDotNet(callId, false, ExceptionDispatchInfo.Capture(exception), assemblyName, methodIdentifier, dotNetObjectId); + var exceptionDispatchInfo = ExceptionDispatchInfo.Capture(t.Exception.GetBaseException()); + var dispatchResult = new DotNetInvocationResult(exceptionDispatchInfo.SourceException, "InvocationFailure"); + jsRuntime.EndInvokeDotNet(invocationInfo, dispatchResult); } var result = TaskGenericsUtil.GetTaskResult(task); - jsRuntime.EndInvokeDotNet(callId, true, result, assemblyName, methodIdentifier, dotNetObjectId); + jsRuntime.EndInvokeDotNet(invocationInfo, new DotNetInvocationResult(result)); }, TaskScheduler.Current); } else { - jsRuntime.EndInvokeDotNet(callId, true, syncResult, assemblyName, methodIdentifier, dotNetObjectId); + var dispatchResult = new DotNetInvocationResult(syncResult); + jsRuntime.EndInvokeDotNet(invocationInfo, dispatchResult); } } - private static object InvokeSynchronously(JSRuntime jsRuntime, string assemblyName, string methodIdentifier, IDotNetObjectReference objectReference, string argsJson) + private static object InvokeSynchronously(JSRuntime jsRuntime, in DotNetInvocationInfo callInfo, IDotNetObjectReference objectReference, string argsJson) { + var assemblyName = callInfo.AssemblyName; + var methodIdentifier = callInfo.MethodIdentifier; + AssemblyKey assemblyKey; if (objectReference is null) { diff --git a/src/JSInterop/Microsoft.JSInterop/src/Infrastructure/DotNetInvocationInfo.cs b/src/JSInterop/Microsoft.JSInterop/src/Infrastructure/DotNetInvocationInfo.cs new file mode 100644 index 0000000000..942fc34da0 --- /dev/null +++ b/src/JSInterop/Microsoft.JSInterop/src/Infrastructure/DotNetInvocationInfo.cs @@ -0,0 +1,48 @@ +// 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. + +namespace Microsoft.JSInterop.Infrastructure +{ + /// + /// Information about a JSInterop call from JavaScript to .NET. + /// + public readonly struct DotNetInvocationInfo + { + /// + /// Initializes a new instance of . + /// + /// The name of the assembly containing the method. + /// The identifier of the method to be invoked. + /// The object identifier for instance method calls. + /// The call identifier. + public DotNetInvocationInfo(string assemblyName, string methodIdentifier, long dotNetObjectId, string callId) + { + CallId = callId; + AssemblyName = assemblyName; + MethodIdentifier = methodIdentifier; + DotNetObjectId = dotNetObjectId; + } + + /// + /// Gets the name of the assembly containing the method. + /// Only one of or may be specified. + /// + public string AssemblyName { get; } + + /// + /// Gets the identifier of the method to be invoked. This is the value specified in the . + /// + public string MethodIdentifier { get; } + + /// + /// Gets the object identifier for instance method calls. + /// Only one of or may be specified. + /// + public long DotNetObjectId { get; } + + /// + /// Gets the call identifier. This value is when the client does not expect a value to be returned. + /// + public string CallId { get; } + } +} diff --git a/src/JSInterop/Microsoft.JSInterop/src/Infrastructure/DotNetInvocationResult.cs b/src/JSInterop/Microsoft.JSInterop/src/Infrastructure/DotNetInvocationResult.cs new file mode 100644 index 0000000000..d62dd532ee --- /dev/null +++ b/src/JSInterop/Microsoft.JSInterop/src/Infrastructure/DotNetInvocationResult.cs @@ -0,0 +1,55 @@ +using System; + +namespace Microsoft.JSInterop.Infrastructure +{ + /// + /// Result of a .NET invocation that is returned to JavaScript. + /// + public readonly struct DotNetInvocationResult + { + /// + /// Constructor for a failed invocation. + /// + /// The that caused the failure. + /// The error kind. + public DotNetInvocationResult(Exception exception, string errorKind) + { + Result = default; + Exception = exception ?? throw new ArgumentNullException(nameof(exception)); + ErrorKind = errorKind; + Success = false; + } + + /// + /// Constructor for a successful invocation. + /// + /// The result. + public DotNetInvocationResult(object result) + { + Result = result; + Exception = default; + ErrorKind = default; + Success = true; + } + + /// + /// Gets the that caused the failure. + /// + public Exception Exception { get; } + + /// + /// Gets the error kind. + /// + public string ErrorKind { get; } + + /// + /// Gets the result of a successful invocation. + /// + public object Result { get; } + + /// + /// if the invocation succeeded, otherwise . + /// + public bool Success { get; } + } +} diff --git a/src/JSInterop/Microsoft.JSInterop/src/JSRuntime.cs b/src/JSInterop/Microsoft.JSInterop/src/JSRuntime.cs index ba411b72db..4dca7a5db3 100644 --- a/src/JSInterop/Microsoft.JSInterop/src/JSRuntime.cs +++ b/src/JSInterop/Microsoft.JSInterop/src/JSRuntime.cs @@ -150,19 +150,11 @@ namespace Microsoft.JSInterop /// /// Completes an async JS interop call from JavaScript to .NET /// - /// The id of the JavaScript callback to execute on completion. - /// Whether the operation succeeded or not. - /// The result of the operation or an object containing error details. - /// The name of the method assembly if the invocation was for a static method. - /// The identifier for the method within the assembly. - /// The tracking id of the dotnet object if the invocation was for an instance method. + /// The . + /// The . protected internal abstract void EndInvokeDotNet( - string callId, - bool success, - object resultOrError, - string assemblyName, - string methodIdentifier, - long dotNetObjectId); + DotNetInvocationInfo invocationInfo, + in DotNetInvocationResult invocationResult); internal void EndInvokeJS(long taskId, bool succeeded, ref Utf8JsonReader jsonReader) { diff --git a/src/JSInterop/Microsoft.JSInterop/test/Infrastructure/DotNetDispatcherTest.cs b/src/JSInterop/Microsoft.JSInterop/test/Infrastructure/DotNetDispatcherTest.cs index 2d8208b7a6..7e82a47a89 100644 --- a/src/JSInterop/Microsoft.JSInterop/test/Infrastructure/DotNetDispatcherTest.cs +++ b/src/JSInterop/Microsoft.JSInterop/test/Infrastructure/DotNetDispatcherTest.cs @@ -20,7 +20,7 @@ namespace Microsoft.JSInterop.Infrastructure { var ex = Assert.Throws(() => { - DotNetDispatcher.Invoke(new TestJSRuntime(), " ", "SomeMethod", default, "[]"); + DotNetDispatcher.Invoke(new TestJSRuntime(), new DotNetInvocationInfo(" ", "SomeMethod", default, default), "[]"); }); Assert.StartsWith("Cannot be null, empty, or whitespace.", ex.Message); @@ -32,7 +32,7 @@ namespace Microsoft.JSInterop.Infrastructure { var ex = Assert.Throws(() => { - DotNetDispatcher.Invoke(new TestJSRuntime(), "SomeAssembly", " ", default, "[]"); + DotNetDispatcher.Invoke(new TestJSRuntime(), new DotNetInvocationInfo("SomeAssembly", " ", default, default), "[]"); }); Assert.StartsWith("Cannot be null, empty, or whitespace.", ex.Message); @@ -45,7 +45,7 @@ namespace Microsoft.JSInterop.Infrastructure var assemblyName = "Some.Fake.Assembly"; var ex = Assert.Throws(() => { - DotNetDispatcher.Invoke(new TestJSRuntime(), assemblyName, "SomeMethod", default, null); + DotNetDispatcher.Invoke(new TestJSRuntime(), new DotNetInvocationInfo(assemblyName, "SomeMethod", default, default), null); }); Assert.Equal($"There is no loaded assembly with the name '{assemblyName}'.", ex.Message); @@ -67,7 +67,7 @@ namespace Microsoft.JSInterop.Infrastructure { var ex = Assert.Throws(() => { - DotNetDispatcher.Invoke(new TestJSRuntime(), thisAssemblyName, methodIdentifier, default, null); + DotNetDispatcher.Invoke(new TestJSRuntime(), new DotNetInvocationInfo(thisAssemblyName, methodIdentifier, default, default), null); }); Assert.Equal($"The assembly '{thisAssemblyName}' does not contain a public method with [JSInvokableAttribute(\"{methodIdentifier}\")].", ex.Message); @@ -79,7 +79,7 @@ namespace Microsoft.JSInterop.Infrastructure // Arrange/Act var jsRuntime = new TestJSRuntime(); SomePublicType.DidInvokeMyInvocableStaticVoid = false; - var resultJson = DotNetDispatcher.Invoke(jsRuntime, thisAssemblyName, "InvocableStaticVoid", default, null); + var resultJson = DotNetDispatcher.Invoke(jsRuntime, new DotNetInvocationInfo(thisAssemblyName, "InvocableStaticVoid", default, default), null); // Assert Assert.Null(resultJson); @@ -91,7 +91,7 @@ namespace Microsoft.JSInterop.Infrastructure { // Arrange/Act var jsRuntime = new TestJSRuntime(); - var resultJson = DotNetDispatcher.Invoke(jsRuntime, thisAssemblyName, "InvocableStaticNonVoid", default, null); + var resultJson = DotNetDispatcher.Invoke(jsRuntime, new DotNetInvocationInfo(thisAssemblyName, "InvocableStaticNonVoid", default, default), null); var result = JsonSerializer.Deserialize(resultJson, jsRuntime.JsonSerializerOptions); // Assert @@ -104,7 +104,7 @@ namespace Microsoft.JSInterop.Infrastructure { // Arrange/Act var jsRuntime = new TestJSRuntime(); - var resultJson = DotNetDispatcher.Invoke(jsRuntime, thisAssemblyName, nameof(SomePublicType.InvokableMethodWithoutCustomIdentifier), default, null); + var resultJson = DotNetDispatcher.Invoke(jsRuntime, new DotNetInvocationInfo(thisAssemblyName, nameof(SomePublicType.InvokableMethodWithoutCustomIdentifier), default, default), null); var result = JsonSerializer.Deserialize(resultJson, jsRuntime.JsonSerializerOptions); // Assert @@ -130,7 +130,7 @@ namespace Microsoft.JSInterop.Infrastructure }, jsRuntime.JsonSerializerOptions); // Act - var resultJson = DotNetDispatcher.Invoke(jsRuntime, thisAssemblyName, "InvocableStaticWithParams", default, argsJson); + var resultJson = DotNetDispatcher.Invoke(jsRuntime, new DotNetInvocationInfo(thisAssemblyName, "InvocableStaticWithParams", default, default), argsJson); var result = JsonDocument.Parse(resultJson); var root = result.RootElement; @@ -171,7 +171,7 @@ namespace Microsoft.JSInterop.Infrastructure // Act & Assert var ex = Assert.Throws(() => - DotNetDispatcher.Invoke(jsRuntime, thisAssemblyName, method, default, argsJson)); + DotNetDispatcher.Invoke(jsRuntime, new DotNetInvocationInfo(thisAssemblyName, method, default, default), argsJson)); Assert.Equal($"In call to '{method}', parameter of type '{nameof(TestDTO)}' at index 3 must be declared as type 'DotNetObjectRef' to receive the incoming value.", ex.Message); } @@ -185,7 +185,7 @@ namespace Microsoft.JSInterop.Infrastructure jsRuntime.Invoke("unimportant", objectRef); // Act - var resultJson = DotNetDispatcher.Invoke(jsRuntime, null, "InvokableInstanceVoid", 1, null); + var resultJson = DotNetDispatcher.Invoke(jsRuntime, new DotNetInvocationInfo(null, "InvokableInstanceVoid", 1, default), null); // Assert Assert.Null(resultJson); @@ -202,7 +202,7 @@ namespace Microsoft.JSInterop.Infrastructure jsRuntime.Invoke("unimportant", objectRef); // Act - var resultJson = DotNetDispatcher.Invoke(jsRuntime, null, "BaseClassInvokableInstanceVoid", 1, null); + var resultJson = DotNetDispatcher.Invoke(jsRuntime, new DotNetInvocationInfo(null, "BaseClassInvokableInstanceVoid", 1, default), null); // Assert Assert.Null(resultJson); @@ -219,7 +219,7 @@ namespace Microsoft.JSInterop.Infrastructure jsRuntime.Invoke("unimportant", objectRef); // Act - DotNetDispatcher.BeginInvokeDotNet(jsRuntime, null, null, "__Dispose", objectRef.ObjectId, null); + DotNetDispatcher.BeginInvokeDotNet(jsRuntime, new DotNetInvocationInfo(null, "__Dispose", objectRef.ObjectId, default), null); // Assert Assert.True(objectRef.Disposed); @@ -240,7 +240,7 @@ namespace Microsoft.JSInterop.Infrastructure // Act/Assert var ex = Assert.Throws( - () => DotNetDispatcher.Invoke(jsRuntime, null, "InvokableInstanceVoid", 1, null)); + () => DotNetDispatcher.Invoke(jsRuntime, new DotNetInvocationInfo(null, "InvokableInstanceVoid", 1, default), null)); Assert.StartsWith("There is no tracked object with id '1'.", ex.Message); } @@ -259,7 +259,7 @@ namespace Microsoft.JSInterop.Infrastructure // Act/Assert var ex = Assert.Throws( - () => DotNetDispatcher.Invoke(jsRuntime, null, "InvokableInstanceVoid", 1, null)); + () => DotNetDispatcher.Invoke(jsRuntime, new DotNetInvocationInfo(null, "InvokableInstanceVoid", 1, default), null)); Assert.StartsWith("There is no tracked object with id '1'.", ex.Message); } @@ -346,7 +346,7 @@ namespace Microsoft.JSInterop.Infrastructure var argsJson = "[\"myvalue\",{\"__dotNetObject\":2}]"; // Act - var resultJson = DotNetDispatcher.Invoke(jsRuntime, null, "InvokableInstanceMethod", 1, argsJson); + var resultJson = DotNetDispatcher.Invoke(jsRuntime, new DotNetInvocationInfo(null, "InvokableInstanceMethod", 1, default), argsJson); // Assert Assert.Equal("[\"You passed myvalue\",{\"__dotNetObject\":3}]", resultJson); @@ -369,7 +369,7 @@ namespace Microsoft.JSInterop.Infrastructure // Act/Assert var ex = Assert.Throws(() => { - DotNetDispatcher.Invoke(jsRuntime, thisAssemblyName, "InvocableStaticWithParams", default, argsJson); + DotNetDispatcher.Invoke(jsRuntime, new DotNetInvocationInfo(thisAssemblyName, "InvocableStaticWithParams", default, default), argsJson); }); Assert.Equal("The call to 'InvocableStaticWithParams' expects '3' parameters, but received '2'.", ex.Message); @@ -392,7 +392,7 @@ namespace Microsoft.JSInterop.Infrastructure // Act/Assert var ex = Assert.Throws(() => { - DotNetDispatcher.Invoke(jsRuntime, thisAssemblyName, "InvocableStaticWithParams", default, argsJson); + DotNetDispatcher.Invoke(jsRuntime, new DotNetInvocationInfo(thisAssemblyName, "InvocableStaticWithParams", default, default), argsJson); }); Assert.Equal("Unexpected JSON token Number. Ensure that the call to `InvocableStaticWithParams' is supplied with exactly '3' parameters.", ex.Message); @@ -419,13 +419,13 @@ namespace Microsoft.JSInterop.Infrastructure // Act var callId = "123"; var resultTask = jsRuntime.NextInvocationTask; - DotNetDispatcher.BeginInvokeDotNet(jsRuntime, callId, null, "InvokableAsyncMethod", 1, argsJson); + DotNetDispatcher.BeginInvokeDotNet(jsRuntime, new DotNetInvocationInfo(null, "InvokableAsyncMethod", 1, callId), argsJson); await resultTask; // Assert: Correct completion information Assert.Equal(callId, jsRuntime.LastCompletionCallId); - Assert.True(jsRuntime.LastCompletionStatus); - var result = Assert.IsType(jsRuntime.LastCompletionResult); + Assert.True(jsRuntime.LastCompletionResult.Success); + var result = Assert.IsType(jsRuntime.LastCompletionResult.Result); var resultDto1 = Assert.IsType(result[0]); Assert.Equal("STRING VIA JSON", resultDto1.StringVal); @@ -447,18 +447,17 @@ namespace Microsoft.JSInterop.Infrastructure // Act var callId = "123"; var resultTask = jsRuntime.NextInvocationTask; - DotNetDispatcher.BeginInvokeDotNet(jsRuntime, callId, thisAssemblyName, nameof(ThrowingClass.ThrowingMethod), default, default); + DotNetDispatcher.BeginInvokeDotNet(jsRuntime, new DotNetInvocationInfo(thisAssemblyName, nameof(ThrowingClass.ThrowingMethod), default, callId), default); await resultTask; // This won't throw, it sets properties on the jsRuntime. // Assert Assert.Equal(callId, jsRuntime.LastCompletionCallId); - Assert.False(jsRuntime.LastCompletionStatus); // Fails + Assert.False(jsRuntime.LastCompletionResult.Success); // Fails // Make sure the method that threw the exception shows up in the call stack // https://github.com/aspnet/AspNetCore/issues/8612 - var exception = jsRuntime.LastCompletionResult is ExceptionDispatchInfo edi ? edi.SourceException.ToString() : null; - Assert.Contains(nameof(ThrowingClass.ThrowingMethod), exception); + Assert.Contains(nameof(ThrowingClass.ThrowingMethod), jsRuntime.LastCompletionResult.Exception.ToString()); } [Fact] @@ -470,18 +469,17 @@ namespace Microsoft.JSInterop.Infrastructure // Act var callId = "123"; var resultTask = jsRuntime.NextInvocationTask; - DotNetDispatcher.BeginInvokeDotNet(jsRuntime, callId, thisAssemblyName, nameof(ThrowingClass.AsyncThrowingMethod), default, default); + DotNetDispatcher.BeginInvokeDotNet(jsRuntime, new DotNetInvocationInfo(thisAssemblyName, nameof(ThrowingClass.AsyncThrowingMethod), default, callId), default); await resultTask; // This won't throw, it sets properties on the jsRuntime. // Assert Assert.Equal(callId, jsRuntime.LastCompletionCallId); - Assert.False(jsRuntime.LastCompletionStatus); // Fails + Assert.False(jsRuntime.LastCompletionResult.Success); // Fails // Make sure the method that threw the exception shows up in the call stack // https://github.com/aspnet/AspNetCore/issues/8612 - var exception = jsRuntime.LastCompletionResult is ExceptionDispatchInfo edi ? edi.SourceException.ToString() : null; - Assert.Contains(nameof(ThrowingClass.AsyncThrowingMethod), exception); + Assert.Contains(nameof(ThrowingClass.AsyncThrowingMethod), jsRuntime.LastCompletionResult.Exception.ToString()); } [Fact] @@ -491,15 +489,15 @@ namespace Microsoft.JSInterop.Infrastructure var jsRuntime = new TestJSRuntime(); var callId = "123"; var resultTask = jsRuntime.NextInvocationTask; - DotNetDispatcher.BeginInvokeDotNet(jsRuntime, callId, thisAssemblyName, "InvocableStaticWithParams", default, "not json"); + DotNetDispatcher.BeginInvokeDotNet(jsRuntime, new DotNetInvocationInfo(thisAssemblyName, "InvocableStaticWithParams", default, callId), "not json"); await resultTask; // This won't throw, it sets properties on the jsRuntime. // Assert Assert.Equal(callId, jsRuntime.LastCompletionCallId); - Assert.False(jsRuntime.LastCompletionStatus); // Fails - var result = Assert.IsType(jsRuntime.LastCompletionResult); - Assert.Contains("JsonReaderException: '<' is an invalid start of a value.", result.SourceException.ToString()); + Assert.False(jsRuntime.LastCompletionResult.Success); // Fails + var exception = jsRuntime.LastCompletionResult.Exception; + Assert.Contains("JsonReaderException: '<' is an invalid start of a value.", exception.ToString()); } [Fact] @@ -509,13 +507,13 @@ namespace Microsoft.JSInterop.Infrastructure var jsRuntime = new TestJSRuntime(); var callId = "123"; var resultTask = jsRuntime.NextInvocationTask; - DotNetDispatcher.BeginInvokeDotNet(jsRuntime, callId, null, "InvokableInstanceVoid", 1, null); + DotNetDispatcher.BeginInvokeDotNet(jsRuntime, new DotNetInvocationInfo(null, "InvokableInstanceVoid", 1, callId), null); // Assert Assert.Equal(callId, jsRuntime.LastCompletionCallId); - Assert.False(jsRuntime.LastCompletionStatus); // Fails - var result = Assert.IsType(jsRuntime.LastCompletionResult); - Assert.StartsWith("System.ArgumentException: There is no tracked object with id '1'. Perhaps the DotNetObjectReference instance was already disposed.", result.SourceException.ToString()); + Assert.False(jsRuntime.LastCompletionResult.Success); // Fails + var exception = jsRuntime.LastCompletionResult.Exception; + Assert.StartsWith("System.ArgumentException: There is no tracked object with id '1'. Perhaps the DotNetObjectReference instance was already disposed.", exception.ToString()); } [Theory] @@ -801,8 +799,7 @@ namespace Microsoft.JSInterop.Infrastructure public string LastInvocationArgsJson { get; private set; } public string LastCompletionCallId { get; private set; } - public bool LastCompletionStatus { get; private set; } - public object LastCompletionResult { get; private set; } + public DotNetInvocationResult LastCompletionResult { get; private set; } protected override void BeginInvokeJS(long asyncHandle, string identifier, string argsJson) { @@ -823,17 +820,10 @@ namespace Microsoft.JSInterop.Infrastructure return null; } - protected internal override void EndInvokeDotNet( - string callId, - bool success, - object resultOrError, - string assemblyName, - string methodIdentifier, - long dotNetObjectId) + protected internal override void EndInvokeDotNet(DotNetInvocationInfo invocationInfo, in DotNetInvocationResult invocationResult) { - LastCompletionCallId = callId; - LastCompletionStatus = success; - LastCompletionResult = resultOrError; + LastCompletionCallId = invocationInfo.CallId; + LastCompletionResult = invocationResult; _nextInvocationTcs.SetResult(null); _nextInvocationTcs = new TaskCompletionSource(); } diff --git a/src/JSInterop/Microsoft.JSInterop/test/JSInProcessRuntimeTest.cs b/src/JSInterop/Microsoft.JSInterop/test/JSInProcessRuntimeTest.cs index a1caff595b..f42e0801a0 100644 --- a/src/JSInterop/Microsoft.JSInterop/test/JSInProcessRuntimeTest.cs +++ b/src/JSInterop/Microsoft.JSInterop/test/JSInProcessRuntimeTest.cs @@ -4,6 +4,7 @@ using System; using System.Collections.Generic; using System.Linq; +using Microsoft.JSInterop.Infrastructure; using Xunit; namespace Microsoft.JSInterop @@ -113,8 +114,8 @@ namespace Microsoft.JSInterop protected override void BeginInvokeJS(long asyncHandle, string identifier, string argsJson) => throw new NotImplementedException("This test only covers sync calls"); - protected internal override void EndInvokeDotNet(string callId, bool success, object resultOrError, string assemblyName, string methodIdentifier, long dotNetObjectId) => - throw new NotImplementedException("This test only covers sync calls"); + protected internal override void EndInvokeDotNet(DotNetInvocationInfo invocationInfo, in DotNetInvocationResult invocationResult) + => throw new NotImplementedException("This test only covers sync calls"); } } } diff --git a/src/JSInterop/Microsoft.JSInterop/test/JSRuntimeTest.cs b/src/JSInterop/Microsoft.JSInterop/test/JSRuntimeTest.cs index b102ecc0b5..66e0033d2a 100644 --- a/src/JSInterop/Microsoft.JSInterop/test/JSRuntimeTest.cs +++ b/src/JSInterop/Microsoft.JSInterop/test/JSRuntimeTest.cs @@ -8,6 +8,7 @@ using System.Text; using System.Text.Json; using System.Threading; using System.Threading.Tasks; +using Microsoft.JSInterop.Infrastructure; using Xunit; namespace Microsoft.JSInterop @@ -296,18 +297,20 @@ namespace Microsoft.JSInterop var expectedMessage = "An error ocurred while invoking '[Assembly]::Method'. Swapping to 'Development' environment will " + "display more detailed information about the error that occurred."; - string GetMessage(string assembly, string method) => $"An error ocurred while invoking '[{assembly}]::{method}'. Swapping to 'Development' environment will " + + string GetMessage(DotNetInvocationInfo info) => $"An error ocurred while invoking '[{info.AssemblyName}]::{info.MethodIdentifier}'. Swapping to 'Development' environment will " + "display more detailed information about the error that occurred."; var runtime = new TestJSRuntime() { - OnDotNetException = (e, a, m) => new JSError { Message = GetMessage(a, m) } + OnDotNetException = (invocationInfo) => new JSError { Message = GetMessage(invocationInfo) } }; var exception = new Exception("Some really sensitive data in here"); + var invocation = new DotNetInvocationInfo("Assembly", "Method", 0, "0"); + var result = new DotNetInvocationResult(exception, default); // Act - runtime.EndInvokeDotNet("0", false, exception, "Assembly", "Method", 0); + runtime.EndInvokeDotNet(invocation, result); // Assert var call = runtime.EndInvokeDotNetCalls.Single(); @@ -356,20 +359,21 @@ namespace Microsoft.JSInterop public object ResultOrError { get; set; } } - public Func OnDotNetException { get; set; } + public Func OnDotNetException { get; set; } - protected internal override void EndInvokeDotNet(string callId, bool success, object resultOrError, string assemblyName, string methodIdentifier, long dotNetObjectId) + protected internal override void EndInvokeDotNet(DotNetInvocationInfo invocationInfo, in DotNetInvocationResult invocationResult) { - if (OnDotNetException != null && !success) + var resultOrError = invocationResult.Success ? invocationResult.Result : invocationResult.Exception; + if (OnDotNetException != null && !invocationResult.Success) { - resultOrError = OnDotNetException(resultOrError as Exception, assemblyName, methodIdentifier); + resultOrError = OnDotNetException(invocationInfo); } EndInvokeDotNetCalls.Add(new EndInvokeDotNetArgs { - CallId = callId, - Success = success, - ResultOrError = resultOrError + CallId = invocationInfo.CallId, + Success = invocationResult.Success, + ResultOrError = resultOrError, }); } diff --git a/src/JSInterop/Microsoft.JSInterop/test/TestJSRuntime.cs b/src/JSInterop/Microsoft.JSInterop/test/TestJSRuntime.cs index 740f02b8da..db9c5ddd36 100644 --- a/src/JSInterop/Microsoft.JSInterop/test/TestJSRuntime.cs +++ b/src/JSInterop/Microsoft.JSInterop/test/TestJSRuntime.cs @@ -2,6 +2,7 @@ // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; +using Microsoft.JSInterop.Infrastructure; namespace Microsoft.JSInterop { @@ -12,7 +13,7 @@ namespace Microsoft.JSInterop throw new NotImplementedException(); } - protected internal override void EndInvokeDotNet(string callId, bool success, object resultOrError, string assemblyName, string methodIdentifier, long dotNetObjectId) + protected internal override void EndInvokeDotNet(DotNetInvocationInfo invocationInfo, in DotNetInvocationResult invocationResult) { throw new NotImplementedException(); } diff --git a/src/JSInterop/Mono.WebAssembly.Interop/ref/Mono.WebAssembly.Interop.netstandard2.0.cs b/src/JSInterop/Mono.WebAssembly.Interop/ref/Mono.WebAssembly.Interop.netstandard2.0.cs index 0996795ca3..8dd70b946a 100644 --- a/src/JSInterop/Mono.WebAssembly.Interop/ref/Mono.WebAssembly.Interop.netstandard2.0.cs +++ b/src/JSInterop/Mono.WebAssembly.Interop/ref/Mono.WebAssembly.Interop.netstandard2.0.cs @@ -7,7 +7,7 @@ namespace Mono.WebAssembly.Interop { public MonoWebAssemblyJSRuntime() { } protected override void BeginInvokeJS(long asyncHandle, string identifier, string argsJson) { } - protected override void EndInvokeDotNet(string callId, bool success, object resultOrError, string assemblyName, string methodIdentifier, long dotNetObjectId) { } + protected override void EndInvokeDotNet(Microsoft.JSInterop.Infrastructure.DotNetInvocationInfo callInfo, in Microsoft.JSInterop.Infrastructure.DotNetInvocationResult dispatchResult) { } protected static void Initialize(Mono.WebAssembly.Interop.MonoWebAssemblyJSRuntime jsRuntime) { } protected override string InvokeJS(string identifier, string argsJson) { throw null; } public TRes InvokeUnmarshalled(string identifier) { throw null; } diff --git a/src/JSInterop/Mono.WebAssembly.Interop/src/MonoWebAssemblyJSRuntime.cs b/src/JSInterop/Mono.WebAssembly.Interop/src/MonoWebAssemblyJSRuntime.cs index b6b01d754d..654263a123 100644 --- a/src/JSInterop/Mono.WebAssembly.Interop/src/MonoWebAssemblyJSRuntime.cs +++ b/src/JSInterop/Mono.WebAssembly.Interop/src/MonoWebAssemblyJSRuntime.cs @@ -2,7 +2,6 @@ // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; -using System.Runtime.ExceptionServices; using System.Text.Json; using Microsoft.JSInterop; using Microsoft.JSInterop.Infrastructure; @@ -53,7 +52,10 @@ namespace Mono.WebAssembly.Interop // Invoked via Mono's JS interop mechanism (invoke_method) private static string InvokeDotNet(string assemblyName, string methodIdentifier, string dotNetObjectId, string argsJson) - => DotNetDispatcher.Invoke(Instance, assemblyName, methodIdentifier, dotNetObjectId == null ? default : long.Parse(dotNetObjectId), argsJson); + { + var callInfo = new DotNetInvocationInfo(assemblyName, methodIdentifier, dotNetObjectId == null ? default : long.Parse(dotNetObjectId), callId: null); + return DotNetDispatcher.Invoke(Instance, callInfo, argsJson); + } // Invoked via Mono's JS interop mechanism (invoke_method) private static void EndInvokeJS(string argsJson) @@ -78,32 +80,20 @@ namespace Mono.WebAssembly.Interop assemblyName = assemblyNameOrDotNetObjectId; } - DotNetDispatcher.BeginInvokeDotNet(Instance, callId, assemblyName, methodIdentifier, dotNetObjectId, argsJson); + var callInfo = new DotNetInvocationInfo(assemblyName, methodIdentifier, dotNetObjectId, callId); + DotNetDispatcher.BeginInvokeDotNet(Instance, callInfo, argsJson); } - protected override void EndInvokeDotNet( - string callId, - bool success, - object resultOrError, - string assemblyName, - string methodIdentifier, - long dotNetObjectId) + protected override void EndInvokeDotNet(DotNetInvocationInfo callInfo, in DotNetInvocationResult dispatchResult) { // For failures, the common case is to call EndInvokeDotNet with the Exception object. // For these we'll serialize as something that's useful to receive on the JS side. // If the value is not an Exception, we'll just rely on it being directly JSON-serializable. - if (!success && resultOrError is Exception ex) - { - resultOrError = ex.ToString(); - } - else if (!success && resultOrError is ExceptionDispatchInfo edi) - { - resultOrError = edi.SourceException.ToString(); - } + var resultOrError = dispatchResult.Success ? dispatchResult.Result : dispatchResult.Exception.ToString(); // We pass 0 as the async handle because we don't want the JS-side code to // send back any notification (we're just providing a result for an existing async call) - var args = JsonSerializer.Serialize(new[] { callId, success, resultOrError }, JsonSerializerOptions); + var args = JsonSerializer.Serialize(new[] { callInfo.CallId, dispatchResult.Success, resultOrError }, JsonSerializerOptions); BeginInvokeJS(0, "DotNet.jsCallDispatcher.endInvokeDotNetFromJS", args); } From 29e0146528d108c5a45aafb3485aeeb33efb67bd Mon Sep 17 00:00:00 2001 From: James Newton-King Date: Sat, 17 Aug 2019 13:06:53 +1200 Subject: [PATCH 0167/1101] Update TypeNameHelper.cs (dotnet/extensions#2194) \n\nCommit migrated from https://github.com/dotnet/extensions/commit/204a4eef27df6ca694ae4f8b768b91a090786d5c --- src/Shared/TypeNameHelper/TypeNameHelper.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Shared/TypeNameHelper/TypeNameHelper.cs b/src/Shared/TypeNameHelper/TypeNameHelper.cs index 3994a074b6..d08b9b0439 100644 --- a/src/Shared/TypeNameHelper/TypeNameHelper.cs +++ b/src/Shared/TypeNameHelper/TypeNameHelper.cs @@ -7,7 +7,7 @@ using System.Collections.Generic; namespace Microsoft.Extensions.Internal { - internal class TypeNameHelper + internal static class TypeNameHelper { private const char DefaultNestedTypeDelimiter = '+'; From bd300b52204aa73283e9cdd081ee3c0a8c18a7bf Mon Sep 17 00:00:00 2001 From: Doug Bunting <6431421+dougbu@users.noreply.github.com> Date: Fri, 16 Aug 2019 18:28:51 -0700 Subject: [PATCH 0168/1101] Rebuilt ref/ code for Microsoft.JSInterop \n\nCommit migrated from https://github.com/dotnet/extensions/commit/b791bce755e554c0092fe0c7b39dc06289ad229a --- .../ref/Microsoft.JSInterop.netcoreapp5.0.cs | 92 +++++++++++++------ 1 file changed, 65 insertions(+), 27 deletions(-) diff --git a/src/JSInterop/Microsoft.JSInterop/ref/Microsoft.JSInterop.netcoreapp5.0.cs b/src/JSInterop/Microsoft.JSInterop/ref/Microsoft.JSInterop.netcoreapp5.0.cs index 654ae9d617..2d8c51caaf 100644 --- a/src/JSInterop/Microsoft.JSInterop/ref/Microsoft.JSInterop.netcoreapp5.0.cs +++ b/src/JSInterop/Microsoft.JSInterop/ref/Microsoft.JSInterop.netcoreapp5.0.cs @@ -3,22 +3,14 @@ namespace Microsoft.JSInterop { - public static partial class DotNetDispatcher + public static partial class DotNetObjectReference { - public static void BeginInvoke(string callId, string assemblyName, string methodIdentifier, long dotNetObjectId, string argsJson) { } - public static void EndInvoke(string arguments) { } - public static string Invoke(string assemblyName, string methodIdentifier, long dotNetObjectId, string argsJson) { throw null; } - [Microsoft.JSInterop.JSInvokableAttribute("DotNetDispatcher.ReleaseDotNetObject")] - public static void ReleaseDotNetObject(long dotNetObjectId) { } + public static Microsoft.JSInterop.DotNetObjectReference Create(TValue value) where TValue : class { throw null; } } - public static partial class DotNetObjectRef + public sealed partial class DotNetObjectReference : System.IDisposable where TValue : class { - public static Microsoft.JSInterop.DotNetObjectRef Create(TValue value) where TValue : class { throw null; } - } - public sealed partial class DotNetObjectRef : System.IDisposable where TValue : class - { - internal DotNetObjectRef() { } - public TValue Value { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } } + internal DotNetObjectReference() { } + public TValue Value { get { throw null; } } public void Dispose() { } } public partial interface IJSInProcessRuntime : Microsoft.JSInterop.IJSRuntime @@ -27,38 +19,84 @@ namespace Microsoft.JSInterop } public partial interface IJSRuntime { - System.Threading.Tasks.Task InvokeAsync(string identifier, System.Collections.Generic.IEnumerable args, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)); - System.Threading.Tasks.Task InvokeAsync(string identifier, params object[] args); + System.Threading.Tasks.ValueTask InvokeAsync(string identifier, object[] args); + System.Threading.Tasks.ValueTask InvokeAsync(string identifier, System.Threading.CancellationToken cancellationToken, object[] args); } public partial class JSException : System.Exception { public JSException(string message) { } public JSException(string message, System.Exception innerException) { } } - public abstract partial class JSInProcessRuntimeBase : Microsoft.JSInterop.JSRuntimeBase, Microsoft.JSInterop.IJSInProcessRuntime, Microsoft.JSInterop.IJSRuntime + public abstract partial class JSInProcessRuntime : Microsoft.JSInterop.JSRuntime, Microsoft.JSInterop.IJSInProcessRuntime, Microsoft.JSInterop.IJSRuntime { - protected JSInProcessRuntimeBase() { } + protected JSInProcessRuntime() { } protected abstract string InvokeJS(string identifier, string argsJson); public TValue Invoke(string identifier, params object[] args) { throw null; } } + public static partial class JSInProcessRuntimeExtensions + { + public static void InvokeVoid(this Microsoft.JSInterop.IJSInProcessRuntime jsRuntime, string identifier, params object[] args) { } + } [System.AttributeUsageAttribute(System.AttributeTargets.Method, AllowMultiple=true)] - public partial class JSInvokableAttribute : System.Attribute + public sealed partial class JSInvokableAttribute : System.Attribute { public JSInvokableAttribute() { } public JSInvokableAttribute(string identifier) { } public string Identifier { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } } } - public static partial class JSRuntime + public abstract partial class JSRuntime : Microsoft.JSInterop.IJSRuntime { - public static void SetCurrentJSRuntime(Microsoft.JSInterop.IJSRuntime instance) { } - } - public abstract partial class JSRuntimeBase : Microsoft.JSInterop.IJSRuntime - { - protected JSRuntimeBase() { } + protected JSRuntime() { } protected System.TimeSpan? DefaultAsyncTimeout { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } + protected internal System.Text.Json.JsonSerializerOptions JsonSerializerOptions { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } } protected abstract void BeginInvokeJS(long taskId, string identifier, string argsJson); - protected internal abstract void EndInvokeDotNet(string callId, bool success, object resultOrError, string assemblyName, string methodIdentifier, long dotNetObjectId); - public System.Threading.Tasks.Task InvokeAsync(string identifier, System.Collections.Generic.IEnumerable args, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) { throw null; } - public System.Threading.Tasks.Task InvokeAsync(string identifier, params object[] args) { throw null; } + protected internal abstract void EndInvokeDotNet(Microsoft.JSInterop.Infrastructure.DotNetInvocationInfo invocationInfo, in Microsoft.JSInterop.Infrastructure.DotNetInvocationResult invocationResult); + public System.Threading.Tasks.ValueTask InvokeAsync(string identifier, object[] args) { throw null; } + public System.Threading.Tasks.ValueTask InvokeAsync(string identifier, System.Threading.CancellationToken cancellationToken, object[] args) { throw null; } + } + public static partial class JSRuntimeExtensions + { + public static System.Threading.Tasks.ValueTask InvokeAsync(this Microsoft.JSInterop.IJSRuntime jsRuntime, string identifier, params object[] args) { throw null; } + public static System.Threading.Tasks.ValueTask InvokeAsync(this Microsoft.JSInterop.IJSRuntime jsRuntime, string identifier, System.Threading.CancellationToken cancellationToken, params object[] args) { throw null; } + [System.Diagnostics.DebuggerStepThroughAttribute] + public static System.Threading.Tasks.ValueTask InvokeAsync(this Microsoft.JSInterop.IJSRuntime jsRuntime, string identifier, System.TimeSpan timeout, params object[] args) { throw null; } + [System.Diagnostics.DebuggerStepThroughAttribute] + public static System.Threading.Tasks.ValueTask InvokeVoidAsync(this Microsoft.JSInterop.IJSRuntime jsRuntime, string identifier, params object[] args) { throw null; } + [System.Diagnostics.DebuggerStepThroughAttribute] + public static System.Threading.Tasks.ValueTask InvokeVoidAsync(this Microsoft.JSInterop.IJSRuntime jsRuntime, string identifier, System.Threading.CancellationToken cancellationToken, params object[] args) { throw null; } + [System.Diagnostics.DebuggerStepThroughAttribute] + public static System.Threading.Tasks.ValueTask InvokeVoidAsync(this Microsoft.JSInterop.IJSRuntime jsRuntime, string identifier, System.TimeSpan timeout, params object[] args) { throw null; } + } +} +namespace Microsoft.JSInterop.Infrastructure +{ + public static partial class DotNetDispatcher + { + public static void BeginInvokeDotNet(Microsoft.JSInterop.JSRuntime jsRuntime, Microsoft.JSInterop.Infrastructure.DotNetInvocationInfo invocationInfo, string argsJson) { } + public static void EndInvokeJS(Microsoft.JSInterop.JSRuntime jsRuntime, string arguments) { } + public static string Invoke(Microsoft.JSInterop.JSRuntime jsRuntime, in Microsoft.JSInterop.Infrastructure.DotNetInvocationInfo invocationInfo, string argsJson) { throw null; } + } + [System.Runtime.InteropServices.StructLayoutAttribute(System.Runtime.InteropServices.LayoutKind.Sequential)] + public readonly partial struct DotNetInvocationInfo + { + private readonly object _dummy; + private readonly int _dummyPrimitive; + public DotNetInvocationInfo(string assemblyName, string methodIdentifier, long dotNetObjectId, string callId) { throw null; } + public string AssemblyName { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } } + public string CallId { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } } + public long DotNetObjectId { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } } + public string MethodIdentifier { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } } + } + [System.Runtime.InteropServices.StructLayoutAttribute(System.Runtime.InteropServices.LayoutKind.Sequential)] + public readonly partial struct DotNetInvocationResult + { + private readonly object _dummy; + private readonly int _dummyPrimitive; + public DotNetInvocationResult(System.Exception exception, string errorKind) { throw null; } + public DotNetInvocationResult(object result) { throw null; } + public string ErrorKind { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } } + public System.Exception Exception { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } } + public object Result { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } } + public bool Success { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } } } } From 7118601f4c3f899c43de0cbd98aa113d50275067 Mon Sep 17 00:00:00 2001 From: David Gardiner Date: Sat, 17 Aug 2019 21:00:08 +0930 Subject: [PATCH 0169/1101] Correct the spelling of 'EnableEndpointRouting' Fixes #11467 --- src/Analyzers/Analyzers/src/StartupAnalyzer.Diagnostics.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Analyzers/Analyzers/src/StartupAnalyzer.Diagnostics.cs b/src/Analyzers/Analyzers/src/StartupAnalyzer.Diagnostics.cs index 8ef6205631..3185c19f34 100644 --- a/src/Analyzers/Analyzers/src/StartupAnalyzer.Diagnostics.cs +++ b/src/Analyzers/Analyzers/src/StartupAnalyzer.Diagnostics.cs @@ -37,7 +37,7 @@ namespace Microsoft.AspNetCore.Analyzers internal readonly static DiagnosticDescriptor UnsupportedUseMvcWithEndpointRouting = new DiagnosticDescriptor( "MVC1005", "Cannot use UseMvc with Endpoint Routing.", - "Using '{0}' to configure MVC is not supported while using Endpoint Routing. To continue using '{0}', please set 'MvcOptions.EnableEndpointRounting = false' inside '{1}'.", + "Using '{0}' to configure MVC is not supported while using Endpoint Routing. To continue using '{0}', please set 'MvcOptions.EnableEndpointRouting = false' inside '{1}'.", "Usage", DiagnosticSeverity.Warning, isEnabledByDefault: true, From 8987cca7c96bbcf9102b7d299cc833841c6de2b6 Mon Sep 17 00:00:00 2001 From: Brennan Date: Mon, 19 Aug 2019 09:36:43 -0700 Subject: [PATCH 0170/1101] HubConnection implements IAsyncDisposable (#13179) --- ...etCore.SignalR.Client.Core.netstandard2.0.cs | 4 ++-- ...etCore.SignalR.Client.Core.netstandard2.1.cs | 4 ++-- .../csharp/Client.Core/src/HubConnection.cs | 17 ++++++++++++----- .../Client/test/UnitTests/HubConnectionTests.cs | 11 +++++++++++ 4 files changed, 27 insertions(+), 9 deletions(-) diff --git a/src/SignalR/clients/csharp/Client.Core/ref/Microsoft.AspNetCore.SignalR.Client.Core.netstandard2.0.cs b/src/SignalR/clients/csharp/Client.Core/ref/Microsoft.AspNetCore.SignalR.Client.Core.netstandard2.0.cs index a361a4ed03..0635cd850a 100644 --- a/src/SignalR/clients/csharp/Client.Core/ref/Microsoft.AspNetCore.SignalR.Client.Core.netstandard2.0.cs +++ b/src/SignalR/clients/csharp/Client.Core/ref/Microsoft.AspNetCore.SignalR.Client.Core.netstandard2.0.cs @@ -3,7 +3,7 @@ namespace Microsoft.AspNetCore.SignalR.Client { - public partial class HubConnection + public partial class HubConnection : System.IAsyncDisposable { public static readonly System.TimeSpan DefaultHandshakeTimeout; public static readonly System.TimeSpan DefaultKeepAliveInterval; @@ -19,7 +19,7 @@ namespace Microsoft.AspNetCore.SignalR.Client public event System.Func Reconnected { add { } remove { } } public event System.Func Reconnecting { add { } remove { } } [System.Diagnostics.DebuggerStepThroughAttribute] - public System.Threading.Tasks.Task DisposeAsync() { throw null; } + public System.Threading.Tasks.ValueTask DisposeAsync() { throw null; } [System.Diagnostics.DebuggerStepThroughAttribute] public System.Threading.Tasks.Task InvokeCoreAsync(string methodName, System.Type returnType, object[] args, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) { throw null; } public System.IDisposable On(string methodName, System.Type[] parameterTypes, System.Func handler, object state) { throw null; } diff --git a/src/SignalR/clients/csharp/Client.Core/ref/Microsoft.AspNetCore.SignalR.Client.Core.netstandard2.1.cs b/src/SignalR/clients/csharp/Client.Core/ref/Microsoft.AspNetCore.SignalR.Client.Core.netstandard2.1.cs index a361a4ed03..0635cd850a 100644 --- a/src/SignalR/clients/csharp/Client.Core/ref/Microsoft.AspNetCore.SignalR.Client.Core.netstandard2.1.cs +++ b/src/SignalR/clients/csharp/Client.Core/ref/Microsoft.AspNetCore.SignalR.Client.Core.netstandard2.1.cs @@ -3,7 +3,7 @@ namespace Microsoft.AspNetCore.SignalR.Client { - public partial class HubConnection + public partial class HubConnection : System.IAsyncDisposable { public static readonly System.TimeSpan DefaultHandshakeTimeout; public static readonly System.TimeSpan DefaultKeepAliveInterval; @@ -19,7 +19,7 @@ namespace Microsoft.AspNetCore.SignalR.Client public event System.Func Reconnected { add { } remove { } } public event System.Func Reconnecting { add { } remove { } } [System.Diagnostics.DebuggerStepThroughAttribute] - public System.Threading.Tasks.Task DisposeAsync() { throw null; } + public System.Threading.Tasks.ValueTask DisposeAsync() { throw null; } [System.Diagnostics.DebuggerStepThroughAttribute] public System.Threading.Tasks.Task InvokeCoreAsync(string methodName, System.Type returnType, object[] args, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) { throw null; } public System.IDisposable On(string methodName, System.Type[] parameterTypes, System.Func handler, object state) { throw null; } diff --git a/src/SignalR/clients/csharp/Client.Core/src/HubConnection.cs b/src/SignalR/clients/csharp/Client.Core/src/HubConnection.cs index 5e04bd309a..61a3aceb55 100644 --- a/src/SignalR/clients/csharp/Client.Core/src/HubConnection.cs +++ b/src/SignalR/clients/csharp/Client.Core/src/HubConnection.cs @@ -22,7 +22,6 @@ using Microsoft.AspNetCore.SignalR.Internal; using Microsoft.AspNetCore.SignalR.Protocol; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging.Abstractions; -using Microsoft.Extensions.Options; namespace Microsoft.AspNetCore.SignalR.Client { @@ -34,7 +33,7 @@ namespace Microsoft.AspNetCore.SignalR.Client /// Before hub methods can be invoked the connection must be started using . /// Clean up a connection using or . /// - public partial class HubConnection + public partial class HubConnection : IAsyncDisposable { public static readonly TimeSpan DefaultServerTimeout = TimeSpan.FromSeconds(30); // Server ping rate is 15 sec, this is 2 times that. public static readonly TimeSpan DefaultHandshakeTimeout = TimeSpan.FromSeconds(15); @@ -292,8 +291,8 @@ namespace Microsoft.AspNetCore.SignalR.Client /// /// Disposes the . /// - /// A that represents the asynchronous dispose. - public async Task DisposeAsync() + /// A that represents the asynchronous dispose. + public async ValueTask DisposeAsync() { if (!_disposed) { @@ -504,8 +503,16 @@ namespace Microsoft.AspNetCore.SignalR.Client if (disposing) { - (_serviceProvider as IDisposable)?.Dispose(); + // Must set this before calling DisposeAsync because the service provider has a reference to the HubConnection and will try to dispose it again _disposed = true; + if (_serviceProvider is IAsyncDisposable asyncDispose) + { + await asyncDispose.DisposeAsync(); + } + else + { + (_serviceProvider as IDisposable)?.Dispose(); + } } } finally diff --git a/src/SignalR/clients/csharp/Client/test/UnitTests/HubConnectionTests.cs b/src/SignalR/clients/csharp/Client/test/UnitTests/HubConnectionTests.cs index 2c9df93cb8..b303e71ddd 100644 --- a/src/SignalR/clients/csharp/Client/test/UnitTests/HubConnectionTests.cs +++ b/src/SignalR/clients/csharp/Client/test/UnitTests/HubConnectionTests.cs @@ -414,6 +414,17 @@ namespace Microsoft.AspNetCore.SignalR.Client.Tests } } + [Fact] + public async Task CanAwaitUsingHubConnection() + { + using (StartVerifiableLog()) + { + var connection = new TestConnection(); + await using var hubConnection = CreateHubConnection(connection, loggerFactory: LoggerFactory); + await hubConnection.StartAsync().OrTimeout(); + } + } + private class SampleObject { public SampleObject(string foo, int bar) From 5b2f3fb5f7f24ac3e91c5150a55cc411b2b36b76 Mon Sep 17 00:00:00 2001 From: Ben Adams Date: Tue, 20 Aug 2019 02:43:59 +0100 Subject: [PATCH 0171/1101] Betterize Hosting Log (#10102) * Handle encoding * Encode space only as + --- .../src/Internal/HostingApplication.cs | 2 + .../Internal/HostingApplicationDiagnostics.cs | 20 +++--- .../src/Internal/HostingRequestFinishedLog.cs | 49 +++++++------ .../src/Internal/HostingRequestStartingLog.cs | 68 ++++++++----------- .../FunctionalTests/Http2/ShutdownTests.cs | 2 +- 5 files changed, 70 insertions(+), 71 deletions(-) diff --git a/src/Hosting/Hosting/src/Internal/HostingApplication.cs b/src/Hosting/Hosting/src/Internal/HostingApplication.cs index c64426c0db..84363221ba 100644 --- a/src/Hosting/Hosting/src/Internal/HostingApplication.cs +++ b/src/Hosting/Hosting/src/Internal/HostingApplication.cs @@ -114,6 +114,7 @@ namespace Microsoft.AspNetCore.Hosting public HttpContext HttpContext { get; set; } public IDisposable Scope { get; set; } public Activity Activity { get; set; } + internal HostingRequestStartingLog StartLog { get; set; } public long StartTimestamp { get; set; } internal bool HasDiagnosticListener { get; set; } @@ -125,6 +126,7 @@ namespace Microsoft.AspNetCore.Hosting Scope = null; Activity = null; + StartLog = null; StartTimestamp = 0; HasDiagnosticListener = false; diff --git a/src/Hosting/Hosting/src/Internal/HostingApplicationDiagnostics.cs b/src/Hosting/Hosting/src/Internal/HostingApplicationDiagnostics.cs index 65730d67ff..385bcbf46d 100644 --- a/src/Hosting/Hosting/src/Internal/HostingApplicationDiagnostics.cs +++ b/src/Hosting/Hosting/src/Internal/HostingApplicationDiagnostics.cs @@ -78,7 +78,7 @@ namespace Microsoft.AspNetCore.Hosting } // Non-inline - LogRequestStarting(httpContext); + LogRequestStarting(context); } } context.StartTimestamp = startTimestamp; @@ -97,7 +97,7 @@ namespace Microsoft.AspNetCore.Hosting { currentTimestamp = Stopwatch.GetTimestamp(); // Non-inline - LogRequestFinished(httpContext, startTimestamp, currentTimestamp); + LogRequestFinished(context, startTimestamp, currentTimestamp); } if (_diagnosticListener.IsEnabled()) @@ -167,30 +167,34 @@ namespace Microsoft.AspNetCore.Hosting } [MethodImpl(MethodImplOptions.NoInlining)] - private void LogRequestStarting(HttpContext httpContext) + private void LogRequestStarting(HostingApplication.Context context) { // IsEnabled is checked in the caller, so if we are here just log + var startLog = new HostingRequestStartingLog(context.HttpContext); + context.StartLog = startLog; + _logger.Log( logLevel: LogLevel.Information, eventId: LoggerEventIds.RequestStarting, - state: new HostingRequestStartingLog(httpContext), + state: startLog, exception: null, formatter: HostingRequestStartingLog.Callback); } [MethodImpl(MethodImplOptions.NoInlining)] - private void LogRequestFinished(HttpContext httpContext, long startTimestamp, long currentTimestamp) + private void LogRequestFinished(HostingApplication.Context context, long startTimestamp, long currentTimestamp) { // IsEnabled isn't checked in the caller, startTimestamp > 0 is used as a fast proxy check - // but that may be because diagnostics are enabled, which also uses startTimestamp, so check here - if (_logger.IsEnabled(LogLevel.Information)) + // but that may be because diagnostics are enabled, which also uses startTimestamp, + // so check if we logged the start event + if (context.StartLog != null) { var elapsed = new TimeSpan((long)(TimestampToTicks * (currentTimestamp - startTimestamp))); _logger.Log( logLevel: LogLevel.Information, eventId: LoggerEventIds.RequestFinished, - state: new HostingRequestFinishedLog(httpContext, elapsed), + state: new HostingRequestFinishedLog(context, elapsed), exception: null, formatter: HostingRequestFinishedLog.Callback); } diff --git a/src/Hosting/Hosting/src/Internal/HostingRequestFinishedLog.cs b/src/Hosting/Hosting/src/Internal/HostingRequestFinishedLog.cs index 63fd5f0921..09a132851d 100644 --- a/src/Hosting/Hosting/src/Internal/HostingRequestFinishedLog.cs +++ b/src/Hosting/Hosting/src/Internal/HostingRequestFinishedLog.cs @@ -9,51 +9,56 @@ using Microsoft.AspNetCore.Http; namespace Microsoft.AspNetCore.Hosting { + using static HostingRequestStartingLog; + internal class HostingRequestFinishedLog : IReadOnlyList> { internal static readonly Func Callback = (state, exception) => ((HostingRequestFinishedLog)state).ToString(); - private readonly HttpContext _httpContext; - private readonly TimeSpan _elapsed; + private readonly HostingApplication.Context _context; private string _cachedToString; + public TimeSpan Elapsed { get; } - public int Count => 3; + public int Count => 11; public KeyValuePair this[int index] { get { - switch (index) + var request = _context.HttpContext.Request; + var response = _context.HttpContext.Response; + + return index switch { - case 0: - return new KeyValuePair("ElapsedMilliseconds", _elapsed.TotalMilliseconds); - case 1: - return new KeyValuePair("StatusCode", _httpContext.Response.StatusCode); - case 2: - return new KeyValuePair("ContentType", _httpContext.Response.ContentType); - default: - throw new IndexOutOfRangeException(nameof(index)); - } + 0 => new KeyValuePair("ElapsedMilliseconds", Elapsed.TotalMilliseconds), + 1 => new KeyValuePair(nameof(response.StatusCode), response.StatusCode), + 2 => new KeyValuePair(nameof(response.ContentType), response.ContentType), + 3 => new KeyValuePair(nameof(response.ContentLength), response.ContentLength), + 4 => new KeyValuePair(nameof(request.Protocol), request.Protocol), + 5 => new KeyValuePair(nameof(request.Method), request.Method), + 6 => new KeyValuePair(nameof(request.Scheme), request.Scheme), + 7 => new KeyValuePair(nameof(request.Host), request.Host.Value), + 8 => new KeyValuePair(nameof(request.PathBase), request.PathBase.Value), + 9 => new KeyValuePair(nameof(request.Path), request.Path.Value), + 10 => new KeyValuePair(nameof(request.QueryString), request.QueryString.Value), + _ => throw new IndexOutOfRangeException(nameof(index)), + }; } } - public HostingRequestFinishedLog(HttpContext httpContext, TimeSpan elapsed) + public HostingRequestFinishedLog(HostingApplication.Context context, TimeSpan elapsed) { - _httpContext = httpContext; - _elapsed = elapsed; + _context = context; + Elapsed = elapsed; } public override string ToString() { if (_cachedToString == null) { - _cachedToString = string.Format( - CultureInfo.InvariantCulture, - "Request finished in {0}ms {1} {2}", - _elapsed.TotalMilliseconds, - _httpContext.Response.StatusCode, - _httpContext.Response.ContentType); + var response = _context.HttpContext.Response; + _cachedToString = $"Request finished {_context.StartLog.ToStringWithoutPreamble()} - {response.StatusCode.ToString(CultureInfo.InvariantCulture)} {ValueOrEmptyMarker(response.ContentLength)} {EscapedValueOrEmptyMarker(response.ContentType)} {Elapsed.TotalMilliseconds.ToString("0.0000", CultureInfo.InvariantCulture)}ms"; } return _cachedToString; diff --git a/src/Hosting/Hosting/src/Internal/HostingRequestStartingLog.cs b/src/Hosting/Hosting/src/Internal/HostingRequestStartingLog.cs index 279fa06aed..3a7586b1c9 100644 --- a/src/Hosting/Hosting/src/Internal/HostingRequestStartingLog.cs +++ b/src/Hosting/Hosting/src/Internal/HostingRequestStartingLog.cs @@ -5,12 +5,16 @@ using System; using System.Collections; using System.Collections.Generic; using System.Globalization; +using System.Net; using Microsoft.AspNetCore.Http; namespace Microsoft.AspNetCore.Hosting { internal class HostingRequestStartingLog : IReadOnlyList> { + private const string LogPreamble = "Request starting "; + private const string EmptyEntry = "-"; + internal static readonly Func Callback = (state, exception) => ((HostingRequestStartingLog)state).ToString(); private readonly HttpRequest _request; @@ -19,35 +23,19 @@ namespace Microsoft.AspNetCore.Hosting public int Count => 9; - public KeyValuePair this[int index] + public KeyValuePair this[int index] => index switch { - get - { - switch (index) - { - case 0: - return new KeyValuePair("Protocol", _request.Protocol); - case 1: - return new KeyValuePair("Method", _request.Method); - case 2: - return new KeyValuePair("ContentType", _request.ContentType); - case 3: - return new KeyValuePair("ContentLength", _request.ContentLength); - case 4: - return new KeyValuePair("Scheme", _request.Scheme); - case 5: - return new KeyValuePair("Host", _request.Host.ToString()); - case 6: - return new KeyValuePair("PathBase", _request.PathBase.ToString()); - case 7: - return new KeyValuePair("Path", _request.Path.ToString()); - case 8: - return new KeyValuePair("QueryString", _request.QueryString.ToString()); - default: - throw new IndexOutOfRangeException(nameof(index)); - } - } - } + 0 => new KeyValuePair(nameof(_request.Protocol), _request.Protocol), + 1 => new KeyValuePair(nameof(_request.Method), _request.Method), + 2 => new KeyValuePair(nameof(_request.ContentType), _request.ContentType), + 3 => new KeyValuePair(nameof(_request.ContentLength), _request.ContentLength), + 4 => new KeyValuePair(nameof(_request.Scheme), _request.Scheme), + 5 => new KeyValuePair(nameof(_request.Host), _request.Host.Value), + 6 => new KeyValuePair(nameof(_request.PathBase), _request.PathBase.Value), + 7 => new KeyValuePair(nameof(_request.Path), _request.Path.Value), + 8 => new KeyValuePair(nameof(_request.QueryString), _request.QueryString.Value), + _ => throw new IndexOutOfRangeException(nameof(index)), + }; public HostingRequestStartingLog(HttpContext httpContext) { @@ -58,18 +46,8 @@ namespace Microsoft.AspNetCore.Hosting { if (_cachedToString == null) { - _cachedToString = string.Format( - CultureInfo.InvariantCulture, - "Request starting {0} {1} {2}://{3}{4}{5}{6} {7} {8}", - _request.Protocol, - _request.Method, - _request.Scheme, - _request.Host.Value, - _request.PathBase.Value, - _request.Path.Value, - _request.QueryString.Value, - _request.ContentType, - _request.ContentLength); + var request = _request; + _cachedToString = $"{LogPreamble}{request.Protocol} {request.Method} {request.Scheme}://{request.Host.Value}{request.PathBase.Value}{request.Path.Value}{request.QueryString.Value} {EscapedValueOrEmptyMarker(request.ContentType)} {ValueOrEmptyMarker(request.ContentLength)}"; ; } return _cachedToString; @@ -87,5 +65,15 @@ namespace Microsoft.AspNetCore.Hosting { return GetEnumerator(); } + + internal string ToStringWithoutPreamble() + => ToString().Substring(LogPreamble.Length); + + internal static string EscapedValueOrEmptyMarker(string potentialValue) + // Encode space as + + => potentialValue?.Length > 0 ? potentialValue.Replace(' ', '+') : EmptyEntry; + + internal static string ValueOrEmptyMarker(T? potentialValue) where T : struct, IFormattable + => potentialValue?.ToString(null, CultureInfo.InvariantCulture) ?? EmptyEntry; } } diff --git a/src/Servers/Kestrel/test/FunctionalTests/Http2/ShutdownTests.cs b/src/Servers/Kestrel/test/FunctionalTests/Http2/ShutdownTests.cs index 03f7bb9f43..e521d2133b 100644 --- a/src/Servers/Kestrel/test/FunctionalTests/Http2/ShutdownTests.cs +++ b/src/Servers/Kestrel/test/FunctionalTests/Http2/ShutdownTests.cs @@ -96,7 +96,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.FunctionalTests.Http2 await stopTask.DefaultTimeout(); } - Assert.Contains(TestApplicationErrorLogger.Messages, m => m.Message.Contains("Request finished in")); + Assert.Contains(TestApplicationErrorLogger.Messages, m => m.Message.Contains("Request finished ")); Assert.Contains(TestApplicationErrorLogger.Messages, m => m.Message.Contains("is closing.")); Assert.Contains(TestApplicationErrorLogger.Messages, m => m.Message.Contains("is closed. The last processed stream ID was 1.")); } From e0f95cfa6feec402ea34a605965f356720006513 Mon Sep 17 00:00:00 2001 From: Mikael Mengistu Date: Wed, 21 Aug 2019 16:15:39 -0500 Subject: [PATCH 0172/1101] Set the user agent header in the Java client (#13168) --- src/SignalR/clients/java/signalr/build.gradle | 24 +++++++ .../com/microsoft/signalr/HubConnection.java | 1 + .../microsoft/signalr/UserAgentHelper.java | 54 ++++++++++++++ .../java/com/microsoft/signalr/Version.java | 11 +++ .../microsoft/signalr/HubConnectionTest.java | 71 +++++++++++++++++++ .../com/microsoft/signalr/UserAgentTest.java | 54 ++++++++++++++ 6 files changed, 215 insertions(+) create mode 100644 src/SignalR/clients/java/signalr/src/main/java/com/microsoft/signalr/UserAgentHelper.java create mode 100644 src/SignalR/clients/java/signalr/src/main/java/com/microsoft/signalr/Version.java create mode 100644 src/SignalR/clients/java/signalr/src/test/java/com/microsoft/signalr/UserAgentTest.java diff --git a/src/SignalR/clients/java/signalr/build.gradle b/src/SignalR/clients/java/signalr/build.gradle index 61b170a40d..8feab7b9b7 100644 --- a/src/SignalR/clients/java/signalr/build.gradle +++ b/src/SignalR/clients/java/signalr/build.gradle @@ -108,3 +108,27 @@ task generatePOM { } task createPackage(dependsOn: [jar,sourceJar,javadocJar,generatePOM]) + +task generateVersionClass { + inputs.property "version", project.version + outputs.dir "$buildDir/generated" + doFirst { + def versionFile = file("$buildDir/../src/main/java/com/microsoft/signalr/Version.java") + versionFile.parentFile.mkdirs() + versionFile.text = + """ +// 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. + +package com.microsoft.signalr; + +class Version { + public static String getDetailedVersion() { + return "$project.version"; + } +} +""" + } +} + +compileJava.dependsOn generateVersionClass diff --git a/src/SignalR/clients/java/signalr/src/main/java/com/microsoft/signalr/HubConnection.java b/src/SignalR/clients/java/signalr/src/main/java/com/microsoft/signalr/HubConnection.java index aa333fb508..96ecd142c5 100644 --- a/src/SignalR/clients/java/signalr/src/main/java/com/microsoft/signalr/HubConnection.java +++ b/src/SignalR/clients/java/signalr/src/main/java/com/microsoft/signalr/HubConnection.java @@ -327,6 +327,7 @@ public class HubConnection { handshakeResponseSubject = CompletableSubject.create(); handshakeReceived = false; CompletableSubject tokenCompletable = CompletableSubject.create(); + localHeaders.put(UserAgentHelper.getUserAgentName(), UserAgentHelper.createUserAgentString()); if (headers != null) { this.localHeaders.putAll(headers); } diff --git a/src/SignalR/clients/java/signalr/src/main/java/com/microsoft/signalr/UserAgentHelper.java b/src/SignalR/clients/java/signalr/src/main/java/com/microsoft/signalr/UserAgentHelper.java new file mode 100644 index 0000000000..988da567f4 --- /dev/null +++ b/src/SignalR/clients/java/signalr/src/main/java/com/microsoft/signalr/UserAgentHelper.java @@ -0,0 +1,54 @@ +// 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. + +package com.microsoft.signalr; + +public class UserAgentHelper { + + private final static String USER_AGENT = "User-Agent"; + + public static String getUserAgentName() { + return USER_AGENT; + } + + public static String createUserAgentString() { + StringBuilder agentBuilder = new StringBuilder("Microsoft SignalR/"); + + // Parsing version numbers + String detailedVersion = Version.getDetailedVersion(); + agentBuilder.append(getVersion(detailedVersion)); + agentBuilder.append("; ("); + agentBuilder.append(detailedVersion); + agentBuilder.append("; "); + + // Getting the OS name + agentBuilder.append(getOS()); + agentBuilder.append("; Java; "); + + // Vendor and Version + agentBuilder.append(getJavaVersion()); + agentBuilder.append("; "); + agentBuilder.append(getJavaVendor()); + agentBuilder.append(")"); + + return agentBuilder.toString(); + } + + static String getVersion(String detailedVersion) { + // Getting the index of the second . so we can return just the major and minor version. + int shortVersionIndex = detailedVersion.indexOf(".", detailedVersion.indexOf(".") + 1); + return detailedVersion.substring(0, shortVersionIndex); + } + + static String getJavaVendor() { + return System.getProperty("java.vendor"); + } + + static String getJavaVersion() { + return System.getProperty("java.version"); + } + + static String getOS() { + return System.getProperty("os.name"); + } +} diff --git a/src/SignalR/clients/java/signalr/src/main/java/com/microsoft/signalr/Version.java b/src/SignalR/clients/java/signalr/src/main/java/com/microsoft/signalr/Version.java new file mode 100644 index 0000000000..4c73a498f6 --- /dev/null +++ b/src/SignalR/clients/java/signalr/src/main/java/com/microsoft/signalr/Version.java @@ -0,0 +1,11 @@ + +// 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. + +package com.microsoft.signalr; + +class Version { + public static String getDetailedVersion() { + return "99.99.99-dev"; + } +} diff --git a/src/SignalR/clients/java/signalr/src/test/java/com/microsoft/signalr/HubConnectionTest.java b/src/SignalR/clients/java/signalr/src/test/java/com/microsoft/signalr/HubConnectionTest.java index f28adf13e7..051b5fed83 100644 --- a/src/SignalR/clients/java/signalr/src/test/java/com/microsoft/signalr/HubConnectionTest.java +++ b/src/SignalR/clients/java/signalr/src/test/java/com/microsoft/signalr/HubConnectionTest.java @@ -2185,6 +2185,77 @@ class HubConnectionTest { } } + @Test + public void userAgentHeaderIsSet() { + AtomicReference header = new AtomicReference<>(); + TestHttpClient client = new TestHttpClient() + .on("POST", "http://example.com/negotiate", + (req) -> { + header.set(req.getHeaders().get("User-Agent")); + return Single.just(new HttpResponse(200, "", "{\"connectionId\":\"bVOiRPG8-6YiJ6d7ZcTOVQ\",\"" + + "availableTransports\":[{\"transport\":\"WebSockets\",\"transferFormats\":[\"Text\",\"Binary\"]}]}")); + }); + + MockTransport transport = new MockTransport(); + HubConnection hubConnection = HubConnectionBuilder.create("http://example.com") + .withTransportImplementation(transport) + .withHttpClient(client) + .build(); + + hubConnection.start().timeout(1, TimeUnit.SECONDS).blockingAwait(); + assertEquals(HubConnectionState.CONNECTED, hubConnection.getConnectionState()); + hubConnection.stop(); + + assertTrue(header.get().startsWith("Microsoft SignalR/")); + } + + @Test + public void userAgentHeaderCanBeOverwritten() { + AtomicReference header = new AtomicReference<>(); + TestHttpClient client = new TestHttpClient() + .on("POST", "http://example.com/negotiate", + (req) -> { + header.set(req.getHeaders().get("User-Agent")); + return Single.just(new HttpResponse(200, "", "{\"connectionId\":\"bVOiRPG8-6YiJ6d7ZcTOVQ\",\"" + + "availableTransports\":[{\"transport\":\"WebSockets\",\"transferFormats\":[\"Text\",\"Binary\"]}]}")); + }); + + MockTransport transport = new MockTransport(); + HubConnection hubConnection = HubConnectionBuilder.create("http://example.com") + .withTransportImplementation(transport) + .withHttpClient(client) + .withHeader("User-Agent", "Updated Value") + .build(); + + hubConnection.start().timeout(1, TimeUnit.SECONDS).blockingAwait(); + assertEquals(HubConnectionState.CONNECTED, hubConnection.getConnectionState()); + hubConnection.stop(); + assertEquals("Updated Value", header.get()); + } + + @Test + public void userAgentCanBeCleared() { + AtomicReference header = new AtomicReference<>(); + TestHttpClient client = new TestHttpClient() + .on("POST", "http://example.com/negotiate", + (req) -> { + header.set(req.getHeaders().get("User-Agent")); + return Single.just(new HttpResponse(200, "", "{\"connectionId\":\"bVOiRPG8-6YiJ6d7ZcTOVQ\",\"" + + "availableTransports\":[{\"transport\":\"WebSockets\",\"transferFormats\":[\"Text\",\"Binary\"]}]}")); + }); + + MockTransport transport = new MockTransport(); + HubConnection hubConnection = HubConnectionBuilder.create("http://example.com") + .withTransportImplementation(transport) + .withHttpClient(client) + .withHeader("User-Agent", "") + .build(); + + hubConnection.start().timeout(1, TimeUnit.SECONDS).blockingAwait(); + assertEquals(HubConnectionState.CONNECTED, hubConnection.getConnectionState()); + hubConnection.stop(); + assertEquals("", header.get()); + } @Test public void headersAreSetAndSentThroughBuilder() { AtomicReference header = new AtomicReference<>(); diff --git a/src/SignalR/clients/java/signalr/src/test/java/com/microsoft/signalr/UserAgentTest.java b/src/SignalR/clients/java/signalr/src/test/java/com/microsoft/signalr/UserAgentTest.java new file mode 100644 index 0000000000..59715223b9 --- /dev/null +++ b/src/SignalR/clients/java/signalr/src/test/java/com/microsoft/signalr/UserAgentTest.java @@ -0,0 +1,54 @@ +// 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. + +package com.microsoft.signalr; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; + +import java.util.stream.Stream; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; + +public class UserAgentTest { + + private static Stream Versions() { + return Stream.of( + Arguments.of("1.0.0", "1.0"), + Arguments.of("3.1.4-preview9-12345", "3.1"), + Arguments.of("3.1.4-preview9-12345-extrastuff", "3.1"), + Arguments.of("99.99.99-dev", "99.99")); + } + + @ParameterizedTest + @MethodSource("Versions") + public void getVersionFromDetailedVersion(String detailedVersion, String version) { + assertEquals(version, UserAgentHelper.getVersion(detailedVersion)); + } + + @Test + public void verifyJavaVendor(){ + assertEquals(System.getProperty("java.vendor"), UserAgentHelper.getJavaVendor()); + } + + @Test + public void verifyJavaVersion(){ + assertEquals(System.getProperty("java.version"), UserAgentHelper.getJavaVersion()); + } + + @Test + public void checkUserAgentString(){ + String userAgent = UserAgentHelper.createUserAgentString(); + assertNotNull(userAgent); + + String detailedVersion = Version.getDetailedVersion(); + String handMadeUserAgent = "Microsoft SignalR/" + UserAgentHelper.getVersion(detailedVersion) + + "; (" + detailedVersion + "; " + UserAgentHelper.getOS() + "; Java; " + + UserAgentHelper.getJavaVersion() + "; " + UserAgentHelper.getJavaVendor() + ")"; + + assertEquals(handMadeUserAgent, userAgent); + } +} From 1153f0d1def3b13fa0ca482a9480c43cc7c29224 Mon Sep 17 00:00:00 2001 From: Doug Bunting <6431421+dougbu@users.noreply.github.com> Date: Thu, 22 Aug 2019 14:25:13 -0700 Subject: [PATCH 0173/1101] Add switch to enable expected and unexpected arguments in any order (dotnet/extensions#2210) - unblocks work on aspnet/AspNetCoredotnet/extensions#4923 - arguments for inside and outside men of service reference doc gen tool are mixed by default - users may add either argument type to the end of the outside man's command line - e.g. "command --unexpected unexpectedValue --expected" can now set the "expected" option - only "--unexpected" and "unexpectedValue" are added to RemainingArguments in that case - default behaviour of the command-line parser is unchanged to avoid breaking existing applications - new switch is supported only when calling `CommandLineApplication` constructor for top-level commands - `dotnet-getdocument` (the outside man) has no (sub)commands and expanding scope would increase churn nits: take VS suggestions in changed files\n\nCommit migrated from https://github.com/dotnet/extensions/commit/e4433979b657848d0cb3b925b7284368931d3772 --- .../CommandLine/CommandLineApplication.cs | 69 ++- .../CommandLineApplicationTests.cs | 505 +++++++++++++++++- 2 files changed, 549 insertions(+), 25 deletions(-) diff --git a/src/Shared/CommandLineUtils/CommandLine/CommandLineApplication.cs b/src/Shared/CommandLineUtils/CommandLine/CommandLineApplication.cs index b7c62b5a0b..51590d5990 100644 --- a/src/Shared/CommandLineUtils/CommandLine/CommandLineApplication.cs +++ b/src/Shared/CommandLineUtils/CommandLine/CommandLineApplication.cs @@ -13,14 +13,23 @@ namespace Microsoft.Extensions.CommandLineUtils { internal class CommandLineApplication { - // Indicates whether the parser should throw an exception when it runs into an unexpected argument. - // If this field is set to false, the parser will stop parsing when it sees an unexpected argument, and all - // remaining arguments, including the first unexpected argument, will be stored in RemainingArguments property. + // Indicates whether the parser should throw an exception when it runs into an unexpected argument. If this is + // set to true (the default), the parser will throw on the first unexpected argument. Otherwise, all unexpected + // arguments (including the first) are added to RemainingArguments. private readonly bool _throwOnUnexpectedArg; - public CommandLineApplication(bool throwOnUnexpectedArg = true) + // Indicates whether the parser should check remaining arguments for command or option matches after + // encountering an unexpected argument. Ignored if _throwOnUnexpectedArg is true (the default). If + // _throwOnUnexpectedArg and this are both false, the first unexpected argument and all remaining arguments are + // added to RemainingArguments. If _throwOnUnexpectedArg is false and this is true, only unexpected arguments + // are added to RemainingArguments -- allowing a mix of expected and unexpected arguments, commands and + // options. + private readonly bool _continueAfterUnexpectedArg; + + public CommandLineApplication(bool throwOnUnexpectedArg = true, bool continueAfterUnexpectedArg = false) { _throwOnUnexpectedArg = throwOnUnexpectedArg; + _continueAfterUnexpectedArg = continueAfterUnexpectedArg; Options = new List(); Arguments = new List(); Commands = new List(); @@ -145,6 +154,7 @@ namespace Microsoft.Extensions.CommandLineUtils { shortOption = arg.Substring(1).Split(new[] { ':', '=' }, 2); } + if (longOption != null) { processed = true; @@ -153,13 +163,27 @@ namespace Microsoft.Extensions.CommandLineUtils if (option == null) { - if (string.IsNullOrEmpty(longOptionName) && !command._throwOnUnexpectedArg && AllowArgumentSeparator) + var ignoreContinueAfterUnexpectedArg = false; + if (string.IsNullOrEmpty(longOptionName) && + !command._throwOnUnexpectedArg && + AllowArgumentSeparator) { - // skip over the '--' argument separator + // Skip over the '--' argument separator then consume all remaining arguments. All + // remaining arguments are unconditionally stored for further use. index++; + ignoreContinueAfterUnexpectedArg = true; + } + + if (HandleUnexpectedArg( + command, + args, + index, + argTypeName: "option", + ignoreContinueAfterUnexpectedArg)) + { + continue; } - HandleUnexpectedArg(command, args, index, argTypeName: "option"); break; } @@ -191,6 +215,7 @@ namespace Microsoft.Extensions.CommandLineUtils option = null; } } + if (shortOption != null) { processed = true; @@ -204,7 +229,11 @@ namespace Microsoft.Extensions.CommandLineUtils if (option == null) { - HandleUnexpectedArg(command, args, index, argTypeName: "option"); + if (HandleUnexpectedArg(command, args, index, argTypeName: "option")) + { + continue; + } + break; } @@ -268,6 +297,7 @@ namespace Microsoft.Extensions.CommandLineUtils processed = true; } } + if (!processed) { if (arguments == null) @@ -280,9 +310,14 @@ namespace Microsoft.Extensions.CommandLineUtils arguments.Current.Values.Add(arg); } } + if (!processed) { - HandleUnexpectedArg(command, args, index, argTypeName: "command or argument"); + if (HandleUnexpectedArg(command, args, index, argTypeName: "command or argument")) + { + continue; + } + break; } } @@ -490,17 +525,29 @@ namespace Microsoft.Extensions.CommandLineUtils Out.WriteLine(); } - private void HandleUnexpectedArg(CommandLineApplication command, string[] args, int index, string argTypeName) + private bool HandleUnexpectedArg( + CommandLineApplication command, + string[] args, + int index, + string argTypeName, + bool ignoreContinueAfterUnexpectedArg = false) { if (command._throwOnUnexpectedArg) { command.ShowHint(); throw new CommandParsingException(command, $"Unrecognized {argTypeName} '{args[index]}'"); } + else if (_continueAfterUnexpectedArg && !ignoreContinueAfterUnexpectedArg) + { + // Store argument for further use. + command.RemainingArguments.Add(args[index]); + return true; + } else { - // All remaining arguments are stored for further use + // Store all remaining arguments for later use. command.RemainingArguments.AddRange(new ArraySegment(args, index, args.Length - index)); + return false; } } diff --git a/src/Shared/test/Shared.Tests/CommandLineApplicationTests.cs b/src/Shared/test/Shared.Tests/CommandLineApplicationTests.cs index 44b7ebdaa8..cc6d0d841e 100644 --- a/src/Shared/test/Shared.Tests/CommandLineApplicationTests.cs +++ b/src/Shared/test/Shared.Tests/CommandLineApplicationTests.cs @@ -4,7 +4,6 @@ using System; using System.IO; using System.Linq; -using System.Text; using Microsoft.Extensions.CommandLineUtils; using Xunit; @@ -73,6 +72,30 @@ namespace Microsoft.Extensions.Internal Assert.Contains("three", ex.Message); } + [Fact] + public void ExtraArgumentAddedToRemaining() + { + CommandArgument first = null; + CommandArgument second = null; + + var app = new CommandLineApplication(); + + var testCommand = app.Command("test", c => + { + first = c.Argument("first", "First argument"); + second = c.Argument("second", "Second argument"); + c.OnExecute(() => 0); + }, + throwOnUnexpectedArg: false); + + app.Execute("test", "one", "two", "three"); + + Assert.Equal("one", first.Value); + Assert.Equal("two", second.Value); + var remaining = Assert.Single(testCommand.RemainingArguments); + Assert.Equal("three", remaining); + } + [Fact] public void UnknownCommandCausesException() { @@ -257,6 +280,145 @@ namespace Microsoft.Extensions.Internal Assert.Equal(unexpectedArg, arg); } + [Fact] + public void AllowArgumentBeforeNoValueOption() + { + var app = new CommandLineApplication(); + var argument = app.Argument("first", "first argument"); + var option = app.Option("--first", "first option", CommandOptionType.NoValue); + + app.Execute("one", "--first"); + + Assert.Equal("one", argument.Value); + Assert.True(option.HasValue()); + } + + [Fact] + public void AllowArgumentAfterNoValueOption() + { + var app = new CommandLineApplication(); + var argument = app.Argument("first", "first argument"); + var option = app.Option("--first", "first option", CommandOptionType.NoValue); + + app.Execute("--first", "one"); + + Assert.Equal("one", argument.Value); + Assert.True(option.HasValue()); + } + + [Fact] + public void AllowArgumentBeforeSingleValueOption() + { + var app = new CommandLineApplication(); + var argument = app.Argument("first", "first argument"); + var option = app.Option("--first ", "first option", CommandOptionType.SingleValue); + + app.Execute("one", "--first", "two"); + + Assert.Equal("one", argument.Value); + Assert.Equal("two", option.Value()); + } + + [Fact] + public void AllowArgumentAfterSingleValueOption() + { + var app = new CommandLineApplication(); + var argument = app.Argument("first", "first argument"); + var option = app.Option("--first ", "first option", CommandOptionType.SingleValue); + + app.Execute("--first", "one", "two"); + + Assert.Equal("two", argument.Value); + Assert.Equal("one", option.Value()); + } + + [Fact] + public void AllowNoThrowBehaviorOnUnexpectedArgumentBeforeNoValueOption_Default() + { + var arguments = new[] { "UnexpectedArg", "--first" }; + var app = new CommandLineApplication(throwOnUnexpectedArg: false); + var option = app.Option("--first", "first option", CommandOptionType.NoValue); + + // (does not throw) + app.Execute(arguments); + + Assert.Equal(arguments, app.RemainingArguments.ToArray()); + Assert.False(option.HasValue()); + } + + [Fact] + public void AllowNoThrowBehaviorOnUnexpectedArgumentBeforeNoValueOption_Continue() + { + var unexpectedArg = "UnexpectedArg"; + var app = new CommandLineApplication(throwOnUnexpectedArg: false, continueAfterUnexpectedArg: true); + var option = app.Option("--first", "first option", CommandOptionType.NoValue); + + // (does not throw) + app.Execute(unexpectedArg, "--first"); + + var arg = Assert.Single(app.RemainingArguments); + Assert.Equal(unexpectedArg, arg); + Assert.True(option.HasValue()); + } + + [Fact] + public void AllowNoThrowBehaviorOnUnexpectedArgumentAfterNoValueOption() + { + var unexpectedArg = "UnexpectedArg"; + var app = new CommandLineApplication(throwOnUnexpectedArg: false); + var option = app.Option("--first", "first option", CommandOptionType.NoValue); + + // (does not throw) + app.Execute("--first", unexpectedArg); + + var arg = Assert.Single(app.RemainingArguments); + Assert.Equal(unexpectedArg, arg); + Assert.True(option.HasValue()); + } + + [Fact] + public void AllowNoThrowBehaviorOnUnexpectedArgumentBeforeSingleValueOption_Default() + { + var arguments = new[] { "UnexpectedArg", "--first", "one" }; + var app = new CommandLineApplication(throwOnUnexpectedArg: false); + app.Option("--first", "first option", CommandOptionType.SingleValue); + + // (does not throw) + app.Execute(arguments); + + Assert.Equal(arguments, app.RemainingArguments.ToArray()); + } + + [Fact] + public void AllowNoThrowBehaviorOnUnexpectedArgumentBeforeSingleValueOption_Continue() + { + var unexpectedArg = "UnexpectedArg"; + var app = new CommandLineApplication(throwOnUnexpectedArg: false, continueAfterUnexpectedArg: true); + var option = app.Option("--first", "first option", CommandOptionType.SingleValue); + + // (does not throw) + app.Execute(unexpectedArg, "--first", "one"); + + var arg = Assert.Single(app.RemainingArguments); + Assert.Equal(unexpectedArg, arg); + Assert.Equal("one", option.Value()); + } + + [Fact] + public void AllowNoThrowBehaviorOnUnexpectedArgumentAfterSingleValueOption() + { + var unexpectedArg = "UnexpectedArg"; + var app = new CommandLineApplication(throwOnUnexpectedArg: false); + var option = app.Option("--first", "first option", CommandOptionType.SingleValue); + + // (does not throw) + app.Execute("--first", "one", unexpectedArg); + + var arg = Assert.Single(app.RemainingArguments); + Assert.Equal(unexpectedArg, arg); + Assert.Equal("one", option.Value()); + } + [Fact] public void ThrowsExceptionOnUnexpectedLongOptionByDefault() { @@ -290,6 +452,183 @@ namespace Microsoft.Extensions.Internal Assert.Equal(unexpectedOption, arg); } + [Fact] + public void AllowNoThrowBehaviorOnUnexpectedLongOptionBeforeNoValueOption_Default() + { + var arguments = new[] { "--unexpected", "--first" }; + var app = new CommandLineApplication(throwOnUnexpectedArg: false); + app.Option("--first", "first option", CommandOptionType.NoValue); + + // (does not throw) + app.Execute(arguments); + + Assert.Equal(arguments, app.RemainingArguments.ToArray()); + } + + [Fact] + public void AllowNoThrowBehaviorOnUnexpectedLongOptionBeforeNoValueOption_Continue() + { + var unexpectedOption = "--unexpected"; + var app = new CommandLineApplication(throwOnUnexpectedArg: false, continueAfterUnexpectedArg: true); + var option = app.Option("--first", "first option", CommandOptionType.NoValue); + + // (does not throw) + app.Execute(unexpectedOption, "--first"); + + var arg = Assert.Single(app.RemainingArguments); + Assert.Equal(unexpectedOption, arg); + Assert.True(option.HasValue()); + } + + [Fact] + public void AllowNoThrowBehaviorOnUnexpectedLongOptionAfterNoValueOption() + { + var unexpectedOption = "--unexpected"; + var app = new CommandLineApplication(throwOnUnexpectedArg: false); + var option = app.Option("--first", "first option", CommandOptionType.NoValue); + + // (does not throw) + app.Execute("--first", unexpectedOption); + + var arg = Assert.Single(app.RemainingArguments); + Assert.Equal(unexpectedOption, arg); + Assert.True(option.HasValue()); + } + + [Fact] + public void AllowNoThrowBehaviorOnUnexpectedLongOptionBeforeSingleValueOption_Default() + { + var arguments = new[] { "--unexpected", "--first", "one" }; + var app = new CommandLineApplication(throwOnUnexpectedArg: false); + app.Option("--first", "first option", CommandOptionType.SingleValue); + + // (does not throw) + app.Execute(arguments); + + Assert.Equal(arguments, app.RemainingArguments.ToArray()); + } + + [Fact] + public void AllowNoThrowBehaviorOnUnexpectedLongOptionBeforeSingleValueOption_Continue() + { + var unexpectedOption = "--unexpected"; + var app = new CommandLineApplication(throwOnUnexpectedArg: false, continueAfterUnexpectedArg: true); + var option = app.Option("--first", "first option", CommandOptionType.SingleValue); + + // (does not throw) + app.Execute(unexpectedOption, "--first", "one"); + + var arg = Assert.Single(app.RemainingArguments); + Assert.Equal(unexpectedOption, arg); + Assert.Equal("one", option.Value()); + } + + [Fact] + public void AllowNoThrowBehaviorOnUnexpectedLongOptionAfterSingleValueOption() + { + var unexpectedOption = "--unexpected"; + var app = new CommandLineApplication(throwOnUnexpectedArg: false); + var option = app.Option("--first", "first option", CommandOptionType.SingleValue); + + // (does not throw) + app.Execute("--first", "one", unexpectedOption); + + var arg = Assert.Single(app.RemainingArguments); + Assert.Equal(unexpectedOption, arg); + Assert.Equal("one", option.Value()); + } + + [Fact] + public void AllowNoThrowBehaviorOnUnexpectedLongOptionWithValueBeforeNoValueOption_Default() + { + var arguments = new[] { "--unexpected", "value", "--first" }; + var app = new CommandLineApplication(throwOnUnexpectedArg: false); + app.Option("--first", "first option", CommandOptionType.NoValue); + + // (does not throw) + app.Execute(arguments); + + Assert.Equal(arguments, app.RemainingArguments.ToArray()); + } + + [Fact] + public void AllowNoThrowBehaviorOnUnexpectedLongOptionWithValueBeforeNoValueOption_Continue() + { + var unexpectedOption = "--unexpected"; + var unexpectedValue = "value"; + var app = new CommandLineApplication(throwOnUnexpectedArg: false, continueAfterUnexpectedArg: true); + var option = app.Option("--first", "first option", CommandOptionType.NoValue); + + // (does not throw) + app.Execute(unexpectedOption, unexpectedValue, "--first"); + + Assert.Equal(new[] { unexpectedOption, unexpectedValue }, app.RemainingArguments.ToArray()); + Assert.True(option.HasValue()); + } + + [Fact] + public void AllowNoThrowBehaviorOnUnexpectedLongOptionWithValueAfterNoValueOption() + { + var unexpectedOption = "--unexpected"; + var unexpectedValue = "value"; + var app = new CommandLineApplication(throwOnUnexpectedArg: false); + var option = app.Option("--first", "first option", CommandOptionType.NoValue); + + // (does not throw) + app.Execute("--first", unexpectedOption, unexpectedValue); + + Assert.Equal(new[] { unexpectedOption, unexpectedValue }, app.RemainingArguments.ToArray()); + Assert.True(option.HasValue()); + } + + [Fact] + public void AllowNoThrowBehaviorOnUnexpectedLongOptionWithValueBeforeSingleValueOption_Default() + { + var unexpectedOption = "--unexpected"; + var unexpectedValue = "value"; + var app = new CommandLineApplication(throwOnUnexpectedArg: false); + app.Option("--first", "first option", CommandOptionType.SingleValue); + + // (does not throw) + app.Execute(unexpectedOption, unexpectedValue, "--first", "one"); + + Assert.Equal( + new[] { unexpectedOption, unexpectedValue, "--first", "one" }, + app.RemainingArguments.ToArray()); + } + + [Fact] + public void AllowNoThrowBehaviorOnUnexpectedLongOptionWithValueBeforeSingleValueOption_Continue() + { + var unexpectedOption = "--unexpected"; + var unexpectedValue = "value"; + var app = new CommandLineApplication(throwOnUnexpectedArg: false, continueAfterUnexpectedArg: true); + var option = app.Option("--first", "first option", CommandOptionType.SingleValue); + + // (does not throw) + app.Execute(unexpectedOption, unexpectedValue, "--first", "one"); + + Assert.Equal( + new[] { unexpectedOption, unexpectedValue }, + app.RemainingArguments.ToArray()); + Assert.Equal("one", option.Value()); + } + + [Fact] + public void AllowNoThrowBehaviorOnUnexpectedLongOptionWithValueAfterSingleValueOption() + { + var unexpectedOption = "--unexpected"; + var unexpectedValue = "value"; + var app = new CommandLineApplication(throwOnUnexpectedArg: false); + var option = app.Option("--first", "first option", CommandOptionType.SingleValue); + + // (does not throw) + app.Execute("--first", "one", unexpectedOption, unexpectedValue); + + Assert.Equal(new[] { unexpectedOption, unexpectedValue }, app.RemainingArguments.ToArray()); + Assert.Equal("one", option.Value()); + } + [Fact] public void ThrowsExceptionOnUnexpectedShortOptionByDefault() { @@ -373,13 +712,35 @@ namespace Microsoft.Extensions.Internal Assert.Equal($"Unrecognized option '{unexpectedOption}'", exception.Message); } + [Fact] + public void AllowNoThrowBehaviorOnUnexpectedOptionBeforeSubcommand() + { + var unexpectedOption = "--unexpected"; + var app = new CommandLineApplication(); + + CommandLineApplication subCmd = null; + var testCmd = app.Command("k", c => + { + subCmd = c.Command("run", _ => { }); + c.OnExecute(() => 0); + }, + throwOnUnexpectedArg: false); + + // (does not throw) + app.Execute("k", unexpectedOption, "run"); + + Assert.Empty(app.RemainingArguments); + Assert.Equal(new[] { unexpectedOption, "run" }, testCmd.RemainingArguments.ToArray()); + Assert.Empty(subCmd.RemainingArguments); + } + [Fact] public void AllowNoThrowBehaviorOnUnexpectedOptionAfterSubcommand() { var unexpectedOption = "--unexpected"; - CommandLineApplication subCmd = null; var app = new CommandLineApplication(); + CommandLineApplication subCmd = null; var testCmd = app.Command("k", c => { subCmd = c.Command("run", _ => { }, throwOnUnexpectedArg: false); @@ -388,11 +749,44 @@ namespace Microsoft.Extensions.Internal // (does not throw) app.Execute("k", "run", unexpectedOption); + + Assert.Empty(app.RemainingArguments); Assert.Empty(testCmd.RemainingArguments); var arg = Assert.Single(subCmd.RemainingArguments); Assert.Equal(unexpectedOption, arg); } + [Fact] + public void AllowNoThrowBehaviorOnUnexpectedOptionBeforeValidCommand_Default() + { + var arguments = new[] { "--unexpected", "run" }; + var app = new CommandLineApplication(throwOnUnexpectedArg: false); + var commandRan = false; + app.Command("run", c => c.OnExecute(() => { commandRan = true; return 0; })); + app.OnExecute(() => 0); + + app.Execute(arguments); + + Assert.False(commandRan); + Assert.Equal(arguments, app.RemainingArguments.ToArray()); + } + + [Fact] + public void AllowNoThrowBehaviorOnUnexpectedOptionBeforeValidCommand_Continue() + { + var unexpectedOption = "--unexpected"; + var app = new CommandLineApplication(throwOnUnexpectedArg: false, continueAfterUnexpectedArg: true); + var commandRan = false; + app.Command("run", c => c.OnExecute(() => { commandRan = true; return 0; })); + app.OnExecute(() => 0); + + app.Execute(unexpectedOption, "run"); + + Assert.True(commandRan); + var remaining = Assert.Single(app.RemainingArguments); + Assert.Equal(unexpectedOption, remaining); + } + [Fact] public void OptionsCanBeInherited() { @@ -530,6 +924,91 @@ namespace Microsoft.Extensions.Internal Assert.Equal(expectedRemaining, app.RemainingArguments.ToArray()); } + [Theory] + [InlineData(new string[0], new string[0], null, false)] + [InlineData(new[] { "--" }, new[] { "--" }, null, false)] + [InlineData(new[] { "-t", "val" }, new string[0], "val", false)] + [InlineData(new[] { "-t", "val", "--" }, new[] { "--" }, "val", false)] + [InlineData(new[] { "--top", "val", "--", "a" }, new[] { "--", "a" }, "val", false)] + [InlineData(new[] { "-t", "val", "--", "a", "--", "b" }, new[] { "--", "a", "--", "b" }, "val", false)] + [InlineData(new[] { "--help", "--" }, new string[0], null, true)] + [InlineData(new[] { "--version", "--" }, new string[0], null, true)] + public void ArgumentSeparator_TreatedAsUexpected( + string[] input, + string[] expectedRemaining, + string topLevelValue, + bool isShowingInformation) + { + var app = new CommandLineApplication(throwOnUnexpectedArg: false); + var optHelp = app.HelpOption("--help"); + var optVersion = app.VersionOption("--version", "1", "1.0"); + var optTop = app.Option("-t|--top ", "arg for command", CommandOptionType.SingleValue); + + app.Execute(input); + + Assert.Equal(topLevelValue, optTop.Value()); + Assert.Equal(expectedRemaining, app.RemainingArguments.ToArray()); + Assert.Equal(isShowingInformation, app.IsShowingInformation); + + // Help and Version options never get values; parsing ends when encountered. + Assert.False(optHelp.HasValue()); + Assert.False(optVersion.HasValue()); + } + + [Theory] + [InlineData(new[] { "--", "a", "--top", "val" }, new[] { "--", "a", "--top", "val" }, null, false)] + [InlineData(new[] { "--", "--help" }, new[] { "--", "--help" }, null, false)] + [InlineData(new[] { "--", "--version" }, new[] { "--", "--version" }, null, false)] + [InlineData(new[] { "unexpected", "--", "--version" }, new[] { "unexpected", "--", "--version" }, null, false)] + public void ArgumentSeparator_TreatedAsUexpected_Default( + string[] input, + string[] expectedRemaining, + string topLevelValue, + bool isShowingInformation) + { + var app = new CommandLineApplication(throwOnUnexpectedArg: false); + var optHelp = app.HelpOption("--help"); + var optVersion = app.VersionOption("--version", "1", "1.0"); + var optTop = app.Option("-t|--top ", "arg for command", CommandOptionType.SingleValue); + + app.Execute(input); + + Assert.Equal(topLevelValue, optTop.Value()); + Assert.Equal(expectedRemaining, app.RemainingArguments.ToArray()); + Assert.Equal(isShowingInformation, app.IsShowingInformation); + + // Help and Version options never get values; parsing ends when encountered. + Assert.False(optHelp.HasValue()); + Assert.False(optVersion.HasValue()); + } + + [Theory] + [InlineData(new[] { "--", "a", "--top", "val" }, new[] { "--", "a" }, "val", false)] + [InlineData(new[] { "--", "--help" }, new[] { "--" }, null, true)] + [InlineData(new[] { "--", "--version" }, new[] { "--" }, null, true)] + [InlineData(new[] { "unexpected", "--", "--version" }, new[] { "unexpected", "--" }, null, true)] + public void ArgumentSeparator_TreatedAsUexpected_Continue( + string[] input, + string[] expectedRemaining, + string topLevelValue, + bool isShowingInformation) + { + var app = new CommandLineApplication(throwOnUnexpectedArg: false, continueAfterUnexpectedArg: true); + var optHelp = app.HelpOption("--help"); + var optVersion = app.VersionOption("--version", "1", "1.0"); + var optTop = app.Option("-t|--top ", "arg for command", CommandOptionType.SingleValue); + + app.Execute(input); + + Assert.Equal(topLevelValue, optTop.Value()); + Assert.Equal(expectedRemaining, app.RemainingArguments.ToArray()); + Assert.Equal(isShowingInformation, app.IsShowingInformation); + + // Help and Version options never get values; parsing ends when encountered. + Assert.False(optHelp.HasValue()); + Assert.False(optVersion.HasValue()); + } + [Fact] public void HelpTextIgnoresHiddenItems() { @@ -607,20 +1086,18 @@ Examples: [InlineData(new[] { "-h", "-f" }, "some flag")] public void HelpAndVersionOptionStopProcessing(string[] input, string expectedOutData) { - using (var outWriter = new StringWriter()) - { - var app = new CommandLineApplication { Out = outWriter }; - app.HelpOption("-h --help"); - app.VersionOption("-V --version", "1", "1.0"); - var optFlag = app.Option("-f |--flag", "some flag", CommandOptionType.NoValue); + using var outWriter = new StringWriter(); + var app = new CommandLineApplication { Out = outWriter }; + app.HelpOption("-h --help"); + app.VersionOption("-V --version", "1", "1.0"); + var optFlag = app.Option("-f |--flag", "some flag", CommandOptionType.NoValue); - app.Execute(input); + app.Execute(input); - outWriter.Flush(); - var outData = outWriter.ToString(); - Assert.Contains(expectedOutData, outData); - Assert.False(optFlag.HasValue()); - } + outWriter.Flush(); + var outData = outWriter.ToString(); + Assert.Contains(expectedOutData, outData); + Assert.False(optFlag.HasValue()); } // disable inaccurate analyzer error https://github.com/xunit/xunit/issues/1274 From 5c05b9288a1250261a53f0d39559639ec77b5764 Mon Sep 17 00:00:00 2001 From: Mikael Mengistu Date: Thu, 22 Aug 2019 16:45:05 -0500 Subject: [PATCH 0174/1101] Fix User Agent on .NET Client (#13298) --- .../FunctionalTests/HubConnectionTests.cs | 112 ++++++++++++++++++ .../HttpConnectionTests.Transport.cs | 10 +- .../src/HttpConnection.cs | 24 +++- .../src/Internal/Constants.cs | 21 ++-- .../SignalR/test/UserAgentHeaderTest.cs | 34 ++++++ 5 files changed, 188 insertions(+), 13 deletions(-) create mode 100644 src/SignalR/server/SignalR/test/UserAgentHeaderTest.cs diff --git a/src/SignalR/clients/csharp/Client/test/FunctionalTests/HubConnectionTests.cs b/src/SignalR/clients/csharp/Client/test/FunctionalTests/HubConnectionTests.cs index 5c135f381a..8198efd9cf 100644 --- a/src/SignalR/clients/csharp/Client/test/FunctionalTests/HubConnectionTests.cs +++ b/src/SignalR/clients/csharp/Client/test/FunctionalTests/HubConnectionTests.cs @@ -1414,6 +1414,118 @@ namespace Microsoft.AspNetCore.SignalR.Client.FunctionalTests } } + [Fact] + public async Task UserAgentIsSet() + { + using (StartServer(out var server)) + { + var hubConnection = new HubConnectionBuilder() + .WithLoggerFactory(LoggerFactory) + .WithUrl(server.Url + "/default", HttpTransportType.LongPolling, options => + { + options.Headers["X-test"] = "42"; + options.Headers["X-42"] = "test"; + }) + .Build(); + try + { + await hubConnection.StartAsync().OrTimeout(); + var headerValues = await hubConnection.InvokeAsync(nameof(TestHub.GetHeaderValues), new[] { "User-Agent" }).OrTimeout(); + Assert.NotNull(headerValues); + Assert.Single(headerValues); + + var userAgent = headerValues[0]; + + Assert.StartsWith("Microsoft SignalR/", userAgent); + + var majorVersion = typeof(HttpConnection).Assembly.GetName().Version.Major; + var minorVersion = typeof(HttpConnection).Assembly.GetName().Version.Minor; + + Assert.Contains($"{majorVersion}.{minorVersion}", userAgent); + + } + catch (Exception ex) + { + LoggerFactory.CreateLogger().LogError(ex, "{ExceptionType} from test", ex.GetType().FullName); + throw; + } + finally + { + await hubConnection.DisposeAsync().OrTimeout(); + } + } + } + + [Fact] + public async Task UserAgentCanBeCleared() + { + using (StartServer(out var server)) + { + var hubConnection = new HubConnectionBuilder() + .WithLoggerFactory(LoggerFactory) + .WithUrl(server.Url + "/default", HttpTransportType.LongPolling, options => + { + options.Headers["User-Agent"] = ""; + }) + .Build(); + try + { + await hubConnection.StartAsync().OrTimeout(); + var headerValues = await hubConnection.InvokeAsync(nameof(TestHub.GetHeaderValues), new[] { "User-Agent" }).OrTimeout(); + Assert.NotNull(headerValues); + Assert.Single(headerValues); + + var userAgent = headerValues[0]; + + Assert.Null(userAgent); + } + catch (Exception ex) + { + LoggerFactory.CreateLogger().LogError(ex, "{ExceptionType} from test", ex.GetType().FullName); + throw; + } + finally + { + await hubConnection.DisposeAsync().OrTimeout(); + } + } + } + + [Fact] + public async Task UserAgentCanBeSet() + { + using (StartServer(out var server)) + { + var hubConnection = new HubConnectionBuilder() + .WithLoggerFactory(LoggerFactory) + .WithUrl(server.Url + "/default", HttpTransportType.LongPolling, options => + { + options.Headers["User-Agent"] = "User Value"; + }) + .Build(); + try + { + await hubConnection.StartAsync().OrTimeout(); + var headerValues = await hubConnection.InvokeAsync(nameof(TestHub.GetHeaderValues), new[] { "User-Agent" }).OrTimeout(); + Assert.NotNull(headerValues); + Assert.Single(headerValues); + + var userAgent = headerValues[0]; + + Assert.Equal("User Value", userAgent); + } + catch (Exception ex) + { + LoggerFactory.CreateLogger().LogError(ex, "{ExceptionType} from test", ex.GetType().FullName); + throw; + } + finally + { + await hubConnection.DisposeAsync().OrTimeout(); + } + } + } + [ConditionalFact] [WebSocketsSupportedCondition] public async Task WebSocketOptionsAreApplied() diff --git a/src/SignalR/clients/csharp/Client/test/UnitTests/HttpConnectionTests.Transport.cs b/src/SignalR/clients/csharp/Client/test/UnitTests/HttpConnectionTests.Transport.cs index 142e40546c..0244af0afd 100644 --- a/src/SignalR/clients/csharp/Client/test/UnitTests/HttpConnectionTests.Transport.cs +++ b/src/SignalR/clients/csharp/Client/test/UnitTests/HttpConnectionTests.Transport.cs @@ -3,6 +3,7 @@ using System; using System.IO.Pipelines; +using System.Linq; using System.Net; using System.Net.Http; using System.Reflection; @@ -113,16 +114,17 @@ namespace Microsoft.AspNetCore.SignalR.Client.Tests testHttpHandler.OnRequest(async (request, next, token) => { - var userAgentHeaderCollection = request.Headers.UserAgent; - var userAgentHeader = Assert.Single(userAgentHeaderCollection); - Assert.Equal("Microsoft.AspNetCore.Http.Connections.Client", userAgentHeader.Product.Name); + var userAgentHeader = request.Headers.UserAgent.ToString(); + + Assert.NotNull(userAgentHeader); + Assert.StartsWith("Microsoft SignalR/", userAgentHeader); // user agent version should come from version embedded in assembly metadata var assemblyVersion = typeof(Constants) .Assembly .GetCustomAttribute(); - Assert.Equal(assemblyVersion.InformationalVersion, userAgentHeader.Product.Version); + Assert.Contains(assemblyVersion.InformationalVersion, userAgentHeader); requestsExecuted = true; diff --git a/src/SignalR/clients/csharp/Http.Connections.Client/src/HttpConnection.cs b/src/SignalR/clients/csharp/Http.Connections.Client/src/HttpConnection.cs index 852681d963..7fde2079dc 100644 --- a/src/SignalR/clients/csharp/Http.Connections.Client/src/HttpConnection.cs +++ b/src/SignalR/clients/csharp/Http.Connections.Client/src/HttpConnection.cs @@ -551,14 +551,34 @@ namespace Microsoft.AspNetCore.Http.Connections.Client httpClient.Timeout = HttpClientTimeout; // Start with the user agent header - httpClient.DefaultRequestHeaders.UserAgent.Add(Constants.UserAgentHeader); + httpClient.DefaultRequestHeaders.Add(Constants.UserAgent, Constants.UserAgentHeader); // Apply any headers configured on the HttpConnectionOptions if (_httpConnectionOptions?.Headers != null) { foreach (var header in _httpConnectionOptions.Headers) { - httpClient.DefaultRequestHeaders.Add(header.Key, header.Value); + // Check if the key is User-Agent and remove if empty string then replace if it exists. + if (string.Equals(header.Key, Constants.UserAgent, StringComparison.OrdinalIgnoreCase)) + { + if (string.IsNullOrEmpty(header.Value)) + { + httpClient.DefaultRequestHeaders.Remove(header.Key); + } + else if (httpClient.DefaultRequestHeaders.Contains(header.Key)) + { + httpClient.DefaultRequestHeaders.Remove(header.Key); + httpClient.DefaultRequestHeaders.Add(header.Key, header.Value); + } + else + { + httpClient.DefaultRequestHeaders.Add(header.Key, header.Value); + } + } + else + { + httpClient.DefaultRequestHeaders.Add(header.Key, header.Value); + } } } diff --git a/src/SignalR/clients/csharp/Http.Connections.Client/src/Internal/Constants.cs b/src/SignalR/clients/csharp/Http.Connections.Client/src/Internal/Constants.cs index 22b41d56f3..c99c7db1a0 100644 --- a/src/SignalR/clients/csharp/Http.Connections.Client/src/Internal/Constants.cs +++ b/src/SignalR/clients/csharp/Http.Connections.Client/src/Internal/Constants.cs @@ -3,19 +3,18 @@ using System.Diagnostics; using System.Linq; -using System.Net.Http.Headers; using System.Reflection; +using System.Runtime.InteropServices; namespace Microsoft.AspNetCore.Http.Connections.Client.Internal { internal static class Constants { - public static readonly ProductInfoHeaderValue UserAgentHeader; + public const string UserAgent = "User-Agent"; + public static readonly string UserAgentHeader; static Constants() { - var userAgent = "Microsoft.AspNetCore.Http.Connections.Client"; - var assemblyVersion = typeof(Constants) .Assembly .GetCustomAttributes() @@ -23,14 +22,22 @@ namespace Microsoft.AspNetCore.Http.Connections.Client.Internal Debug.Assert(assemblyVersion != null); + var majorVersion = typeof(Constants).Assembly.GetName().Version.Major; + var minorVersion = typeof(Constants).Assembly.GetName().Version.Minor; + var os = RuntimeInformation.OSDescription; + var runtime = ".NET"; + var runtimeVersion = RuntimeInformation.FrameworkDescription; + // assembly version attribute should always be present // but in case it isn't then don't include version in user-agent if (assemblyVersion != null) { - userAgent += "/" + assemblyVersion.InformationalVersion; + UserAgentHeader = $"Microsoft SignalR/{majorVersion}.{minorVersion} ({assemblyVersion.InformationalVersion}; {os}; {runtime}; {runtimeVersion})"; + } + else + { + UserAgentHeader = $"Microsoft SignalR/{majorVersion}.{minorVersion} ({os}; {runtime}; {runtimeVersion})"; } - - UserAgentHeader = ProductInfoHeaderValue.Parse(userAgent); } } } diff --git a/src/SignalR/server/SignalR/test/UserAgentHeaderTest.cs b/src/SignalR/server/SignalR/test/UserAgentHeaderTest.cs new file mode 100644 index 0000000000..6c3ba450d8 --- /dev/null +++ b/src/SignalR/server/SignalR/test/UserAgentHeaderTest.cs @@ -0,0 +1,34 @@ +// 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.Linq; +using System.Reflection; +using System.Runtime.InteropServices; +using Microsoft.AspNetCore.Http.Connections.Client; +using Xunit; +using Constants = Microsoft.AspNetCore.Http.Connections.Client.Internal.Constants; + +namespace Microsoft.AspNetCore.Http.Connections.Tests +{ + public class UserAgentHeaderTest + { + [Fact] + public void UserAgentHeaderIsAccurate() + { + var majorVersion = typeof(HttpConnection).Assembly.GetName().Version.Major; + var minorVersion = typeof(HttpConnection).Assembly.GetName().Version.Minor; + var version = typeof(HttpConnection).Assembly.GetName().Version; + var os = RuntimeInformation.OSDescription; + var runtime = ".NET"; + var runtimeVersion = RuntimeInformation.FrameworkDescription; + var assemblyVersion = typeof(Constants) + .Assembly + .GetCustomAttributes() + .FirstOrDefault(); + var userAgent = Constants.UserAgentHeader; + var expectedUserAgent = $"Microsoft SignalR/{majorVersion}.{minorVersion} ({assemblyVersion.InformationalVersion}; {os}; {runtime}; {runtimeVersion})"; + + Assert.Equal(expectedUserAgent, userAgent); + } + } +} From 8b861ea7d717eb09673d4ca648dc96ebe36fe425 Mon Sep 17 00:00:00 2001 From: Brian Friesen Date: Fri, 23 Aug 2019 18:32:25 -0400 Subject: [PATCH 0175/1101] Add CreateXmlReader overload - adds Type parameter (#13302) * Add CreateXmlReader overload - adds Type parameter This change allows inheritors of XmlSerializerInputFormatter to return a different XmlReader depending on the type being serialized. For example, an inheritor could return XSD-validating readers that validate against one schema or another (or perhaps not validate at all), depending on the value of the type parameter. --- ...spNetCore.Mvc.Formatters.Xml.netcoreapp3.0.cs | 1 + .../src/XmlSerializerInputFormatter.cs | 16 ++++++++++++++-- 2 files changed, 15 insertions(+), 2 deletions(-) diff --git a/src/Mvc/Mvc.Formatters.Xml/ref/Microsoft.AspNetCore.Mvc.Formatters.Xml.netcoreapp3.0.cs b/src/Mvc/Mvc.Formatters.Xml/ref/Microsoft.AspNetCore.Mvc.Formatters.Xml.netcoreapp3.0.cs index fa9ffb071d..eae4a1a733 100644 --- a/src/Mvc/Mvc.Formatters.Xml/ref/Microsoft.AspNetCore.Mvc.Formatters.Xml.netcoreapp3.0.cs +++ b/src/Mvc/Mvc.Formatters.Xml/ref/Microsoft.AspNetCore.Mvc.Formatters.Xml.netcoreapp3.0.cs @@ -47,6 +47,7 @@ namespace Microsoft.AspNetCore.Mvc.Formatters protected override bool CanReadType(System.Type type) { throw null; } protected virtual System.Xml.Serialization.XmlSerializer CreateSerializer(System.Type type) { throw null; } protected virtual System.Xml.XmlReader CreateXmlReader(System.IO.Stream readStream, System.Text.Encoding encoding) { throw null; } + protected virtual System.Xml.XmlReader CreateXmlReader(System.IO.Stream readStream, System.Text.Encoding encoding, System.Type type) { throw null; } protected virtual System.Xml.Serialization.XmlSerializer GetCachedSerializer(System.Type type) { throw null; } protected virtual System.Type GetSerializableType(System.Type declaredType) { throw null; } [System.Diagnostics.DebuggerStepThroughAttribute] diff --git a/src/Mvc/Mvc.Formatters.Xml/src/XmlSerializerInputFormatter.cs b/src/Mvc/Mvc.Formatters.Xml/src/XmlSerializerInputFormatter.cs index 4debb6fe69..da40141ded 100644 --- a/src/Mvc/Mvc.Formatters.Xml/src/XmlSerializerInputFormatter.cs +++ b/src/Mvc/Mvc.Formatters.Xml/src/XmlSerializerInputFormatter.cs @@ -119,8 +119,8 @@ namespace Microsoft.AspNetCore.Mvc.Formatters try { - using var xmlReader = CreateXmlReader(readStream, encoding); - var type = GetSerializableType(context.ModelType); + var type = GetSerializableType(context.ModelType); + using var xmlReader = CreateXmlReader(readStream, encoding, type); var serializer = GetCachedSerializer(type); @@ -191,6 +191,18 @@ namespace Microsoft.AspNetCore.Mvc.Formatters return wrapperProvider?.WrappingType ?? declaredType; } + /// + /// Called during deserialization to get the . + /// + /// The from which to read. + /// The used to read the stream. + /// The that is to be deserialized. + /// The used during deserialization. + protected virtual XmlReader CreateXmlReader(Stream readStream, Encoding encoding, Type type) + { + return CreateXmlReader(readStream, encoding); + } + /// /// Called during deserialization to get the . /// From f0029227cf5ed4f13e2b5ce41c44cbfa763f79a5 Mon Sep 17 00:00:00 2001 From: John Luo Date: Fri, 23 Aug 2019 16:03:04 -0700 Subject: [PATCH 0176/1101] Skip site extensions builds in PRs (#13352) --- .azure/pipelines/ci.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.azure/pipelines/ci.yml b/.azure/pipelines/ci.yml index 67d59a247f..9faf14391d 100644 --- a/.azure/pipelines/ci.yml +++ b/.azure/pipelines/ci.yml @@ -95,6 +95,7 @@ jobs: -pack -noBuildDeps $(_BuildArgs) + condition: ne(variables['Build.Reason'], 'PullRequest') displayName: Build SiteExtension # This runs code-signing on all packages, zips, and jar files as defined in build/CodeSign.targets. If https://github.com/dotnet/arcade/issues/1957 is resolved, From 20dd338b791991a4bef3158543032eed7b43a05e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nikolaj=20J=C3=B8rgensen?= Date: Sat, 24 Aug 2019 03:30:59 +0200 Subject: [PATCH 0177/1101] Use ModelOnly for Validation Summary (#6108) Previously when Validation Summary used "All", it would append the property-related content to both the summary as well as the property specific span elements (= asp-validation-for="..."). By switching to ModelOnly would only show model-related messages in the summary (such as "Invalid login attempt.") which was most likely the original intention. --- .../UI/src/Areas/Identity/Pages/V3/Account/ExternalLogin.cshtml | 2 +- .../src/Areas/Identity/Pages/V3/Account/ForgotPassword.cshtml | 2 +- .../UI/src/Areas/Identity/Pages/V3/Account/Login.cshtml | 2 +- .../UI/src/Areas/Identity/Pages/V3/Account/LoginWith2fa.cshtml | 2 +- .../Identity/Pages/V3/Account/LoginWithRecoveryCode.cshtml | 2 +- .../Identity/Pages/V3/Account/Manage/ChangePassword.cshtml | 2 +- .../Identity/Pages/V3/Account/Manage/DeletePersonalData.cshtml | 2 +- .../UI/src/Areas/Identity/Pages/V3/Account/Manage/Index.cshtml | 2 +- .../Areas/Identity/Pages/V3/Account/Manage/SetPassword.cshtml | 2 +- .../UI/src/Areas/Identity/Pages/V3/Account/Register.cshtml | 2 +- .../UI/src/Areas/Identity/Pages/V3/Account/ResetPassword.cshtml | 2 +- .../UI/src/Areas/Identity/Pages/V4/Account/ExternalLogin.cshtml | 2 +- .../src/Areas/Identity/Pages/V4/Account/ForgotPassword.cshtml | 2 +- .../UI/src/Areas/Identity/Pages/V4/Account/Login.cshtml | 2 +- .../UI/src/Areas/Identity/Pages/V4/Account/LoginWith2fa.cshtml | 2 +- .../Identity/Pages/V4/Account/LoginWithRecoveryCode.cshtml | 2 +- .../Identity/Pages/V4/Account/Manage/ChangePassword.cshtml | 2 +- .../Identity/Pages/V4/Account/Manage/DeletePersonalData.cshtml | 2 +- .../UI/src/Areas/Identity/Pages/V4/Account/Manage/Index.cshtml | 2 +- .../Areas/Identity/Pages/V4/Account/Manage/SetPassword.cshtml | 2 +- .../UI/src/Areas/Identity/Pages/V4/Account/Register.cshtml | 2 +- .../UI/src/Areas/Identity/Pages/V4/Account/ResetPassword.cshtml | 2 +- .../Areas/Identity/Pages/Account/Manage/Index.cshtml | 2 +- .../Areas/Identity/Pages/Account/Register.cshtml | 2 +- .../Views/Account/ExternalLoginConfirmation.cshtml | 2 +- .../IdentitySample.Mvc/Views/Account/ForgotPassword.cshtml | 2 +- .../samples/IdentitySample.Mvc/Views/Account/Login.cshtml | 2 +- .../samples/IdentitySample.Mvc/Views/Account/Register.cshtml | 2 +- .../IdentitySample.Mvc/Views/Account/ResetPassword.cshtml | 2 +- .../IdentitySample.Mvc/Views/Account/UseRecoveryCode.cshtml | 2 +- .../Views/Account/VerifyAuthenticatorCode.cshtml | 2 +- .../samples/IdentitySample.Mvc/Views/Account/VerifyCode.cshtml | 2 +- .../IdentitySample.Mvc/Views/Manage/AddPhoneNumber.cshtml | 2 +- .../IdentitySample.Mvc/Views/Manage/ChangePassword.cshtml | 2 +- .../samples/IdentitySample.Mvc/Views/Manage/SetPassword.cshtml | 2 +- .../IdentitySample.Mvc/Views/Manage/VerifyPhoneNumber.cshtml | 2 +- .../Identity.DefaultUI.WebSite/Pages/Contoso/Login.cshtml | 2 +- 37 files changed, 37 insertions(+), 37 deletions(-) diff --git a/src/Identity/UI/src/Areas/Identity/Pages/V3/Account/ExternalLogin.cshtml b/src/Identity/UI/src/Areas/Identity/Pages/V3/Account/ExternalLogin.cshtml index e0053be7fd..f73e2fe556 100644 --- a/src/Identity/UI/src/Areas/Identity/Pages/V3/Account/ExternalLogin.cshtml +++ b/src/Identity/UI/src/Areas/Identity/Pages/V3/Account/ExternalLogin.cshtml @@ -17,7 +17,7 @@
-
+
diff --git a/src/Identity/UI/src/Areas/Identity/Pages/V3/Account/ForgotPassword.cshtml b/src/Identity/UI/src/Areas/Identity/Pages/V3/Account/ForgotPassword.cshtml index b938724210..7541125984 100644 --- a/src/Identity/UI/src/Areas/Identity/Pages/V3/Account/ForgotPassword.cshtml +++ b/src/Identity/UI/src/Areas/Identity/Pages/V3/Account/ForgotPassword.cshtml @@ -10,7 +10,7 @@
-
+
diff --git a/src/Identity/UI/src/Areas/Identity/Pages/V3/Account/Login.cshtml b/src/Identity/UI/src/Areas/Identity/Pages/V3/Account/Login.cshtml index a58edae8a8..8ebfba2d1b 100644 --- a/src/Identity/UI/src/Areas/Identity/Pages/V3/Account/Login.cshtml +++ b/src/Identity/UI/src/Areas/Identity/Pages/V3/Account/Login.cshtml @@ -12,7 +12,7 @@

Use a local account to log in.


-
+
diff --git a/src/Identity/UI/src/Areas/Identity/Pages/V3/Account/LoginWith2fa.cshtml b/src/Identity/UI/src/Areas/Identity/Pages/V3/Account/LoginWith2fa.cshtml index 94242c929e..28b23ae8e1 100644 --- a/src/Identity/UI/src/Areas/Identity/Pages/V3/Account/LoginWith2fa.cshtml +++ b/src/Identity/UI/src/Areas/Identity/Pages/V3/Account/LoginWith2fa.cshtml @@ -11,7 +11,7 @@
-
+
diff --git a/src/Identity/UI/src/Areas/Identity/Pages/V3/Account/LoginWithRecoveryCode.cshtml b/src/Identity/UI/src/Areas/Identity/Pages/V3/Account/LoginWithRecoveryCode.cshtml index abaad2a4d4..9f0e526bcf 100644 --- a/src/Identity/UI/src/Areas/Identity/Pages/V3/Account/LoginWithRecoveryCode.cshtml +++ b/src/Identity/UI/src/Areas/Identity/Pages/V3/Account/LoginWithRecoveryCode.cshtml @@ -13,7 +13,7 @@
-
+
diff --git a/src/Identity/UI/src/Areas/Identity/Pages/V3/Account/Manage/ChangePassword.cshtml b/src/Identity/UI/src/Areas/Identity/Pages/V3/Account/Manage/ChangePassword.cshtml index 701c1ea457..0cdc9c97a7 100644 --- a/src/Identity/UI/src/Areas/Identity/Pages/V3/Account/Manage/ChangePassword.cshtml +++ b/src/Identity/UI/src/Areas/Identity/Pages/V3/Account/Manage/ChangePassword.cshtml @@ -10,7 +10,7 @@
-
+
diff --git a/src/Identity/UI/src/Areas/Identity/Pages/V3/Account/Manage/DeletePersonalData.cshtml b/src/Identity/UI/src/Areas/Identity/Pages/V3/Account/Manage/DeletePersonalData.cshtml index e1a2b7a8a2..4939c7fd5a 100644 --- a/src/Identity/UI/src/Areas/Identity/Pages/V3/Account/Manage/DeletePersonalData.cshtml +++ b/src/Identity/UI/src/Areas/Identity/Pages/V3/Account/Manage/DeletePersonalData.cshtml @@ -16,7 +16,7 @@
-
+
@if (Model.RequirePassword) {
diff --git a/src/Identity/UI/src/Areas/Identity/Pages/V3/Account/Manage/Index.cshtml b/src/Identity/UI/src/Areas/Identity/Pages/V3/Account/Manage/Index.cshtml index 19ac1a296e..2adbdecf6f 100644 --- a/src/Identity/UI/src/Areas/Identity/Pages/V3/Account/Manage/Index.cshtml +++ b/src/Identity/UI/src/Areas/Identity/Pages/V3/Account/Manage/Index.cshtml @@ -10,7 +10,7 @@
-
+
diff --git a/src/Identity/UI/src/Areas/Identity/Pages/V3/Account/Manage/SetPassword.cshtml b/src/Identity/UI/src/Areas/Identity/Pages/V3/Account/Manage/SetPassword.cshtml index 31632c3860..e82ce19df5 100644 --- a/src/Identity/UI/src/Areas/Identity/Pages/V3/Account/Manage/SetPassword.cshtml +++ b/src/Identity/UI/src/Areas/Identity/Pages/V3/Account/Manage/SetPassword.cshtml @@ -14,7 +14,7 @@
-
+
diff --git a/src/Identity/UI/src/Areas/Identity/Pages/V3/Account/Register.cshtml b/src/Identity/UI/src/Areas/Identity/Pages/V3/Account/Register.cshtml index cf6a7c6e96..78415afe1a 100644 --- a/src/Identity/UI/src/Areas/Identity/Pages/V3/Account/Register.cshtml +++ b/src/Identity/UI/src/Areas/Identity/Pages/V3/Account/Register.cshtml @@ -11,7 +11,7 @@

Create a new account.


-
+
diff --git a/src/Identity/UI/src/Areas/Identity/Pages/V3/Account/ResetPassword.cshtml b/src/Identity/UI/src/Areas/Identity/Pages/V3/Account/ResetPassword.cshtml index 5ccb61edcf..97629b27cd 100644 --- a/src/Identity/UI/src/Areas/Identity/Pages/V3/Account/ResetPassword.cshtml +++ b/src/Identity/UI/src/Areas/Identity/Pages/V3/Account/ResetPassword.cshtml @@ -10,7 +10,7 @@
-
+
diff --git a/src/Identity/UI/src/Areas/Identity/Pages/V4/Account/ExternalLogin.cshtml b/src/Identity/UI/src/Areas/Identity/Pages/V4/Account/ExternalLogin.cshtml index d92664c31c..7579138faa 100644 --- a/src/Identity/UI/src/Areas/Identity/Pages/V4/Account/ExternalLogin.cshtml +++ b/src/Identity/UI/src/Areas/Identity/Pages/V4/Account/ExternalLogin.cshtml @@ -17,7 +17,7 @@
-
+
diff --git a/src/Identity/UI/src/Areas/Identity/Pages/V4/Account/ForgotPassword.cshtml b/src/Identity/UI/src/Areas/Identity/Pages/V4/Account/ForgotPassword.cshtml index d3eb7ce65d..4570844df8 100644 --- a/src/Identity/UI/src/Areas/Identity/Pages/V4/Account/ForgotPassword.cshtml +++ b/src/Identity/UI/src/Areas/Identity/Pages/V4/Account/ForgotPassword.cshtml @@ -10,7 +10,7 @@
-
+
diff --git a/src/Identity/UI/src/Areas/Identity/Pages/V4/Account/Login.cshtml b/src/Identity/UI/src/Areas/Identity/Pages/V4/Account/Login.cshtml index b98655e0f6..7db2738277 100644 --- a/src/Identity/UI/src/Areas/Identity/Pages/V4/Account/Login.cshtml +++ b/src/Identity/UI/src/Areas/Identity/Pages/V4/Account/Login.cshtml @@ -12,7 +12,7 @@

Use a local account to log in.


-
+
diff --git a/src/Identity/UI/src/Areas/Identity/Pages/V4/Account/LoginWith2fa.cshtml b/src/Identity/UI/src/Areas/Identity/Pages/V4/Account/LoginWith2fa.cshtml index b5508902f6..5cc2aa2d13 100644 --- a/src/Identity/UI/src/Areas/Identity/Pages/V4/Account/LoginWith2fa.cshtml +++ b/src/Identity/UI/src/Areas/Identity/Pages/V4/Account/LoginWith2fa.cshtml @@ -11,7 +11,7 @@
-
+
diff --git a/src/Identity/UI/src/Areas/Identity/Pages/V4/Account/LoginWithRecoveryCode.cshtml b/src/Identity/UI/src/Areas/Identity/Pages/V4/Account/LoginWithRecoveryCode.cshtml index 957f72b00f..5947903f87 100644 --- a/src/Identity/UI/src/Areas/Identity/Pages/V4/Account/LoginWithRecoveryCode.cshtml +++ b/src/Identity/UI/src/Areas/Identity/Pages/V4/Account/LoginWithRecoveryCode.cshtml @@ -13,7 +13,7 @@
-
+
diff --git a/src/Identity/UI/src/Areas/Identity/Pages/V4/Account/Manage/ChangePassword.cshtml b/src/Identity/UI/src/Areas/Identity/Pages/V4/Account/Manage/ChangePassword.cshtml index 07e23b117c..4d5dda19ec 100644 --- a/src/Identity/UI/src/Areas/Identity/Pages/V4/Account/Manage/ChangePassword.cshtml +++ b/src/Identity/UI/src/Areas/Identity/Pages/V4/Account/Manage/ChangePassword.cshtml @@ -10,7 +10,7 @@
-
+
diff --git a/src/Identity/UI/src/Areas/Identity/Pages/V4/Account/Manage/DeletePersonalData.cshtml b/src/Identity/UI/src/Areas/Identity/Pages/V4/Account/Manage/DeletePersonalData.cshtml index e1896604ff..abcd16a82f 100644 --- a/src/Identity/UI/src/Areas/Identity/Pages/V4/Account/Manage/DeletePersonalData.cshtml +++ b/src/Identity/UI/src/Areas/Identity/Pages/V4/Account/Manage/DeletePersonalData.cshtml @@ -15,7 +15,7 @@
-
+
@if (Model.RequirePassword) {
diff --git a/src/Identity/UI/src/Areas/Identity/Pages/V4/Account/Manage/Index.cshtml b/src/Identity/UI/src/Areas/Identity/Pages/V4/Account/Manage/Index.cshtml index dd25959817..1c125b18f7 100644 --- a/src/Identity/UI/src/Areas/Identity/Pages/V4/Account/Manage/Index.cshtml +++ b/src/Identity/UI/src/Areas/Identity/Pages/V4/Account/Manage/Index.cshtml @@ -10,7 +10,7 @@
-
+
diff --git a/src/Identity/UI/src/Areas/Identity/Pages/V4/Account/Manage/SetPassword.cshtml b/src/Identity/UI/src/Areas/Identity/Pages/V4/Account/Manage/SetPassword.cshtml index 2a42c34f36..593526746e 100644 --- a/src/Identity/UI/src/Areas/Identity/Pages/V4/Account/Manage/SetPassword.cshtml +++ b/src/Identity/UI/src/Areas/Identity/Pages/V4/Account/Manage/SetPassword.cshtml @@ -14,7 +14,7 @@
-
+
diff --git a/src/Identity/UI/src/Areas/Identity/Pages/V4/Account/Register.cshtml b/src/Identity/UI/src/Areas/Identity/Pages/V4/Account/Register.cshtml index 99620a5836..0a2f84f935 100644 --- a/src/Identity/UI/src/Areas/Identity/Pages/V4/Account/Register.cshtml +++ b/src/Identity/UI/src/Areas/Identity/Pages/V4/Account/Register.cshtml @@ -11,7 +11,7 @@

Create a new account.


-
+
diff --git a/src/Identity/UI/src/Areas/Identity/Pages/V4/Account/ResetPassword.cshtml b/src/Identity/UI/src/Areas/Identity/Pages/V4/Account/ResetPassword.cshtml index 3202efb8ec..953261844b 100644 --- a/src/Identity/UI/src/Areas/Identity/Pages/V4/Account/ResetPassword.cshtml +++ b/src/Identity/UI/src/Areas/Identity/Pages/V4/Account/ResetPassword.cshtml @@ -10,7 +10,7 @@
-
+
diff --git a/src/Identity/samples/IdentitySample.DefaultUI/Areas/Identity/Pages/Account/Manage/Index.cshtml b/src/Identity/samples/IdentitySample.DefaultUI/Areas/Identity/Pages/Account/Manage/Index.cshtml index 6b2de21b43..23593ab99a 100644 --- a/src/Identity/samples/IdentitySample.DefaultUI/Areas/Identity/Pages/Account/Manage/Index.cshtml +++ b/src/Identity/samples/IdentitySample.DefaultUI/Areas/Identity/Pages/Account/Manage/Index.cshtml @@ -9,7 +9,7 @@
-
+
diff --git a/src/Identity/samples/IdentitySample.DefaultUI/Areas/Identity/Pages/Account/Register.cshtml b/src/Identity/samples/IdentitySample.DefaultUI/Areas/Identity/Pages/Account/Register.cshtml index 259373e966..42297ce4d6 100644 --- a/src/Identity/samples/IdentitySample.DefaultUI/Areas/Identity/Pages/Account/Register.cshtml +++ b/src/Identity/samples/IdentitySample.DefaultUI/Areas/Identity/Pages/Account/Register.cshtml @@ -11,7 +11,7 @@

Create a new account.


-
+
diff --git a/src/Identity/samples/IdentitySample.Mvc/Views/Account/ExternalLoginConfirmation.cshtml b/src/Identity/samples/IdentitySample.Mvc/Views/Account/ExternalLoginConfirmation.cshtml index 0bd3f75c1a..c9151a4239 100644 --- a/src/Identity/samples/IdentitySample.Mvc/Views/Account/ExternalLoginConfirmation.cshtml +++ b/src/Identity/samples/IdentitySample.Mvc/Views/Account/ExternalLoginConfirmation.cshtml @@ -9,7 +9,7 @@

Association Form


-
+

You've successfully authenticated with @ViewData["ProviderDisplayName"]. diff --git a/src/Identity/samples/IdentitySample.Mvc/Views/Account/ForgotPassword.cshtml b/src/Identity/samples/IdentitySample.Mvc/Views/Account/ForgotPassword.cshtml index 5603e2a08c..74f769a750 100644 --- a/src/Identity/samples/IdentitySample.Mvc/Views/Account/ForgotPassword.cshtml +++ b/src/Identity/samples/IdentitySample.Mvc/Views/Account/ForgotPassword.cshtml @@ -11,7 +11,7 @@ @*

Enter your email.


-
+
diff --git a/src/Identity/samples/IdentitySample.Mvc/Views/Account/Login.cshtml b/src/Identity/samples/IdentitySample.Mvc/Views/Account/Login.cshtml index b8803908e1..92e5a4a89a 100644 --- a/src/Identity/samples/IdentitySample.Mvc/Views/Account/Login.cshtml +++ b/src/Identity/samples/IdentitySample.Mvc/Views/Account/Login.cshtml @@ -15,7 +15,7 @@

Use a local account to log in.


-
+
diff --git a/src/Identity/samples/IdentitySample.Mvc/Views/Account/Register.cshtml b/src/Identity/samples/IdentitySample.Mvc/Views/Account/Register.cshtml index 44e11d6955..f2d23a465c 100644 --- a/src/Identity/samples/IdentitySample.Mvc/Views/Account/Register.cshtml +++ b/src/Identity/samples/IdentitySample.Mvc/Views/Account/Register.cshtml @@ -8,7 +8,7 @@

Create a new account.


-
+
diff --git a/src/Identity/samples/IdentitySample.Mvc/Views/Account/ResetPassword.cshtml b/src/Identity/samples/IdentitySample.Mvc/Views/Account/ResetPassword.cshtml index 343fcd8d33..c815424623 100644 --- a/src/Identity/samples/IdentitySample.Mvc/Views/Account/ResetPassword.cshtml +++ b/src/Identity/samples/IdentitySample.Mvc/Views/Account/ResetPassword.cshtml @@ -8,7 +8,7 @@

Reset your password.


-
+
diff --git a/src/Identity/samples/IdentitySample.Mvc/Views/Account/UseRecoveryCode.cshtml b/src/Identity/samples/IdentitySample.Mvc/Views/Account/UseRecoveryCode.cshtml index 7c74d72a23..dd52938373 100644 --- a/src/Identity/samples/IdentitySample.Mvc/Views/Account/UseRecoveryCode.cshtml +++ b/src/Identity/samples/IdentitySample.Mvc/Views/Account/UseRecoveryCode.cshtml @@ -6,7 +6,7 @@

@ViewData["Title"].

-
+

@ViewData["Status"]


diff --git a/src/Identity/samples/IdentitySample.Mvc/Views/Account/VerifyAuthenticatorCode.cshtml b/src/Identity/samples/IdentitySample.Mvc/Views/Account/VerifyAuthenticatorCode.cshtml index f675dd1535..04ead74dbb 100644 --- a/src/Identity/samples/IdentitySample.Mvc/Views/Account/VerifyAuthenticatorCode.cshtml +++ b/src/Identity/samples/IdentitySample.Mvc/Views/Account/VerifyAuthenticatorCode.cshtml @@ -6,7 +6,7 @@

@ViewData["Title"].

-
+

@ViewData["Status"]


diff --git a/src/Identity/samples/IdentitySample.Mvc/Views/Account/VerifyCode.cshtml b/src/Identity/samples/IdentitySample.Mvc/Views/Account/VerifyCode.cshtml index 330a659ca7..4f15eda730 100644 --- a/src/Identity/samples/IdentitySample.Mvc/Views/Account/VerifyCode.cshtml +++ b/src/Identity/samples/IdentitySample.Mvc/Views/Account/VerifyCode.cshtml @@ -6,7 +6,7 @@

@ViewData["Title"].

-
+

@ViewData["Status"]

diff --git a/src/Identity/samples/IdentitySample.Mvc/Views/Manage/AddPhoneNumber.cshtml b/src/Identity/samples/IdentitySample.Mvc/Views/Manage/AddPhoneNumber.cshtml index 40885f3832..679dcb0a05 100644 --- a/src/Identity/samples/IdentitySample.Mvc/Views/Manage/AddPhoneNumber.cshtml +++ b/src/Identity/samples/IdentitySample.Mvc/Views/Manage/AddPhoneNumber.cshtml @@ -7,7 +7,7 @@

Add a phone number.


-
+
diff --git a/src/Identity/samples/IdentitySample.Mvc/Views/Manage/ChangePassword.cshtml b/src/Identity/samples/IdentitySample.Mvc/Views/Manage/ChangePassword.cshtml index a8d076821b..4d3b38c13c 100644 --- a/src/Identity/samples/IdentitySample.Mvc/Views/Manage/ChangePassword.cshtml +++ b/src/Identity/samples/IdentitySample.Mvc/Views/Manage/ChangePassword.cshtml @@ -8,7 +8,7 @@

Change Password Form


-
+
diff --git a/src/Identity/samples/IdentitySample.Mvc/Views/Manage/SetPassword.cshtml b/src/Identity/samples/IdentitySample.Mvc/Views/Manage/SetPassword.cshtml index d1a1b77a44..ccd5781882 100644 --- a/src/Identity/samples/IdentitySample.Mvc/Views/Manage/SetPassword.cshtml +++ b/src/Identity/samples/IdentitySample.Mvc/Views/Manage/SetPassword.cshtml @@ -11,7 +11,7 @@

Set your password


-
+
diff --git a/src/Identity/samples/IdentitySample.Mvc/Views/Manage/VerifyPhoneNumber.cshtml b/src/Identity/samples/IdentitySample.Mvc/Views/Manage/VerifyPhoneNumber.cshtml index 076c20562c..6c4718a6f7 100644 --- a/src/Identity/samples/IdentitySample.Mvc/Views/Manage/VerifyPhoneNumber.cshtml +++ b/src/Identity/samples/IdentitySample.Mvc/Views/Manage/VerifyPhoneNumber.cshtml @@ -10,7 +10,7 @@

Add a phone number.

@ViewData["Status"]

-
+
diff --git a/src/Identity/testassets/Identity.DefaultUI.WebSite/Pages/Contoso/Login.cshtml b/src/Identity/testassets/Identity.DefaultUI.WebSite/Pages/Contoso/Login.cshtml index 91d36c62b0..0bcefd7555 100644 --- a/src/Identity/testassets/Identity.DefaultUI.WebSite/Pages/Contoso/Login.cshtml +++ b/src/Identity/testassets/Identity.DefaultUI.WebSite/Pages/Contoso/Login.cshtml @@ -10,7 +10,7 @@
-
+
From 90231e7290b22cd99bfba5e0970d2dd679ac4ff8 Mon Sep 17 00:00:00 2001 From: alewmt Date: Sat, 24 Aug 2019 04:32:00 +0300 Subject: [PATCH 0178/1101] Fix invalid test (#6654) --- src/Mvc/Mvc.Core/test/Authorization/AuthorizeFilterTest.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Mvc/Mvc.Core/test/Authorization/AuthorizeFilterTest.cs b/src/Mvc/Mvc.Core/test/Authorization/AuthorizeFilterTest.cs index e5c390395b..15d2341e12 100644 --- a/src/Mvc/Mvc.Core/test/Authorization/AuthorizeFilterTest.cs +++ b/src/Mvc/Mvc.Core/test/Authorization/AuthorizeFilterTest.cs @@ -317,11 +317,11 @@ namespace Microsoft.AspNetCore.Mvc.Authorization public async Task AuthorizationFilterCombinesMultipleFilters() { // Arrange - var authorizeFilter = new AuthorizeFilter(new AuthorizationPolicyBuilder().RequireAssertion(a => true).Build()); + var authorizeFilter = new AuthorizeFilter(new AuthorizationPolicyBuilder().RequireAssertion(a => false).Build()); var authorizationContext = GetAuthorizationContext(anonymous: false); // Effective policy should fail, if both are combined authorizationContext.Filters.Add(authorizeFilter); - var secondFilter = new AuthorizeFilter(new AuthorizationPolicyBuilder().RequireAssertion(a => false).Build()); + var secondFilter = new AuthorizeFilter(new AuthorizationPolicyBuilder().RequireAssertion(a => true).Build()); authorizationContext.Filters.Add(secondFilter); // Act From 460f9b63e9db9f4a3a542fcdc4204e29ca90f123 Mon Sep 17 00:00:00 2001 From: Luke Latham <1622880+guardrex@users.noreply.github.com> Date: Sat, 24 Aug 2019 17:13:25 -0500 Subject: [PATCH 0179/1101] Drop 'en-us' loc segment from URLs + doc nits (#13410) --- src/DataProtection/README.md | 2 +- .../Shared/MainLayout.Auth.razor | 2 +- .../Shared/MainLayout.NoAuth.razor | 2 +- .../Authentication/Certificate/src/README.md | 84 +++++++------------ .../src/MicrosoftChallengeProperties.cs | 2 +- .../CrossMachineReadMe.md | 14 ++-- src/Security/README.md | 4 +- .../samples/Identity.ExternalClaims/README.md | 2 +- .../HttpSys/src/RequestProcessing/Request.cs | 2 +- src/SignalR/README.md | 2 +- .../ts/signalr-protocol-msgpack/README.md | 2 +- src/SignalR/clients/ts/signalr/README.md | 2 +- .../ts/signalr/src/HubConnectionBuilder.ts | 4 +- .../src/Internal/RedisProtocol.cs | 2 +- .../src/Internal/ProcessRunner.cs | 2 +- 15 files changed, 50 insertions(+), 78 deletions(-) diff --git a/src/DataProtection/README.md b/src/DataProtection/README.md index cd58074d9e..4c558753b0 100644 --- a/src/DataProtection/README.md +++ b/src/DataProtection/README.md @@ -1,7 +1,7 @@ DataProtection ============== -Data Protection APIs for protecting and unprotecting data. You can find documentation for Data Protection in the [ASP.NET Core Documentation](https://docs.microsoft.com/en-us/aspnet/core/security/data-protection/). +Data Protection APIs for protecting and unprotecting data. You can find documentation for Data Protection in the [ASP.NET Core Documentation](https://docs.microsoft.com/aspnet/core/security/data-protection/). ## Community Maintained Data Protection Providers & Projects diff --git a/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorServerWeb-CSharp/Shared/MainLayout.Auth.razor b/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorServerWeb-CSharp/Shared/MainLayout.Auth.razor index f2f9098007..842ef1f6bc 100644 --- a/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorServerWeb-CSharp/Shared/MainLayout.Auth.razor +++ b/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorServerWeb-CSharp/Shared/MainLayout.Auth.razor @@ -7,7 +7,7 @@
- About + About
diff --git a/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorServerWeb-CSharp/Shared/MainLayout.NoAuth.razor b/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorServerWeb-CSharp/Shared/MainLayout.NoAuth.razor index 9b5407d03b..74820a0b75 100644 --- a/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorServerWeb-CSharp/Shared/MainLayout.NoAuth.razor +++ b/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorServerWeb-CSharp/Shared/MainLayout.NoAuth.razor @@ -6,7 +6,7 @@
- About + About
diff --git a/src/Security/Authentication/Certificate/src/README.md b/src/Security/Authentication/Certificate/src/README.md index 542131fdf1..b5654819e6 100644 --- a/src/Security/Authentication/Certificate/src/README.md +++ b/src/Security/Authentication/Certificate/src/README.md @@ -1,31 +1,22 @@ # Microsoft.AspNetCore.Authentication.Certificate -This project sort of contains an implementation of [Certificate Authentication](https://tools.ietf.org/html/rfc5246#section-7.4.4) for ASP.NET Core. -Certificate authentication happens at the TLS level, long before it ever gets to ASP.NET Core, so, more accurately this is an authentication handler -that validates the certificate and then gives you an event where you can resolve that certificate to a ClaimsPrincipal. +This project sort of contains an implementation of [Certificate Authentication](https://tools.ietf.org/html/rfc5246#section-7.4.4) for ASP.NET Core. Certificate authentication happens at the TLS level, long before it ever gets to ASP.NET Core, so, more accurately this is an authentication handler that validates the certificate and then gives you an event where you can resolve that certificate to a ClaimsPrincipal. -You **must** [configure your host](#hostConfiguration) for certificate authentication, be it IIS, Kestrel, Azure Web Applications or whatever else you're using. +You **must** [configure your host](#configuring-your-host-to-require-certificates) for certificate authentication, be it IIS, Kestrel, Azure Web Applications or whatever else you're using. ## Getting started -First acquire an HTTPS certificate, apply it and then [configure your host](#hostConfiguration) to require certificates. +First acquire an HTTPS certificate, apply it and then [configure your host](#configuring-your-host-to-require-certificates) to require certificates. -In your web application add a reference to the package, then in the `ConfigureServices` method in `startup.cs` call -`app.AddAuthentication(CertificateAuthenticationDefaults.AuthenticationScheme).UseCertificateAuthentication(...);` with your options, -providing a delegate for `OnValidateCertificate` to validate the client certificate sent with requests and turn that information -into an `ClaimsPrincipal`, set it on the `context.Principal` property and call `context.Success()`. +In your web application add a reference to the package, then in the `ConfigureServices` method in `startup.cs` call `app.AddAuthentication(CertificateAuthenticationDefaults.AuthenticationScheme).UseCertificateAuthentication(...);` with your options, providing a delegate for `OnValidateCertificate` to validate the client certificate sent with requests and turn that information into an `ClaimsPrincipal`, set it on the `context.Principal` property and call `context.Success()`. -If you change your scheme name in the options for the authentication handler you need to change the scheme name in -`AddAuthentication()` to ensure it's used on every request which ends in an endpoint that requires authorization. +If you change your scheme name in the options for the authentication handler you need to change the scheme name in `AddAuthentication()` to ensure it's used on every request which ends in an endpoint that requires authorization. -If authentication fails this handler will return a `403 (Forbidden)` response rather a `401 (Unauthorized)` as you -might expect - this is because the authentication should happen during the initial TLS connection - by the time it -reaches the handler it's too late, and there's no way to actually upgrade the connection from an anonymous connection -to one with a certificate. +If authentication fails this handler will return a `403 (Forbidden)` response rather a `401 (Unauthorized)` as you might expect - this is because the authentication should happen during the initial TLS connection - by the time it reaches the handler it's too late, and there's no way to actually upgrade the connection from an anonymous connection to one with a certificate. You must also add `app.UseAuthentication();` in the `Configure` method, otherwise nothing will ever get called. -For example; +For example: ```c# public void ConfigureServices(IServiceCollection services) @@ -47,25 +38,19 @@ In the sample above you can see the default way to add certificate authenticatio ## Configuring Certificate Validation -The `CertificateAuthenticationOptions` handler has some built in validations that are the minimium validations you should perform on -a certificate. Each of these settings are turned on by default. +The `CertificateAuthenticationOptions` handler has some built in validations that are the minimum validations you should perform on a certificate. Each of these settings are turned on by default. ### ValidateCertificateChain -This check validates that the issuer for the certificate is trusted by the application host OS. If -you are going to accept self-signed certificates you must disable this check. +This check validates that the issuer for the certificate is trusted by the application host OS. If you are going to accept self-signed certificates you must disable this check. ### ValidateCertificateUse -This check validates that the certificate presented by the client has the Client Authentication -extended key use, or no EKUs at all (as the specifications say if no EKU is specified then all EKUs -are valid). +This check validates that the certificate presented by the client has the Client Authentication extended key use, or no EKUs at all (as the specifications say if no EKU is specified then all EKUs are valid). ### ValidateValidityPeriod -This check validates that the certificate is within its validity period. As the handler runs on every -request this ensures that a certificate that was valid when it was presented has not expired during -its current session. +This check validates that the certificate is within its validity period. As the handler runs on every request this ensures that a certificate that was valid when it was presented has not expired during its current session. ### RevocationFlag @@ -73,24 +58,21 @@ A flag which specifies which certificates in the chain are checked for revocatio Revocation checks are only performed when the certificate is chained to a root certificate. -### RevocationMode +### RevocationMode A flag which specifies how revocation checks are performed. + Specifying an on-line check can result in a long delay while the certificate authority is contacted. Revocation checks are only performed when the certificate is chained to a root certificate. ### Can I configure my application to require a certificate only on certain paths? -Not possible, remember the certificate exchange is done that the start of the HTTPS conversation, -it's done by the host, not the application. Kestrel, IIS, Azure Web Apps don't have any configuration for -this sort of thing. +Not possible, remember the certificate exchange is done that the start of the HTTPS conversation, it's done by the host, not the application. Kestrel, IIS, Azure Web Apps don't have any configuration for this sort of thing. -# Handler events +## Handler events -The handler has two events, `OnAuthenticationFailed()`, which is called if an exception happens during authentication and allows you to react, and `OnValidateCertificate()` which is -called after certificate has been validated, passed validation, abut before the default principal has been created. This allows you to perform your own validation, for example -checking if the certificate is one your services knows about, and to construct your own principal. For example, +The handler has two events, `OnAuthenticationFailed()`, which is called if an exception happens during authentication and allows you to react, and `OnValidateCertificate()` which is called after certificate has been validated, passed validation, abut before the default principal has been created. This allows you to perform your own validation, for example checking if the certificate is one your services knows about, and to construct your own principal. For example: ```c# services.AddAuthentication(CertificateAuthenticationDefaults.AuthenticationScheme) @@ -117,8 +99,7 @@ services.AddAuthentication(CertificateAuthenticationDefaults.AuthenticationSchem If you find the inbound certificate doesn't meet your extra validation call `context.Fail("failure Reason")` with a failure reason. -For real functionality you will probably want to call a service registered in DI which talks to a database or other type of -user store. You can grab your service by using the context passed into your delegates, like so +For real functionality you will probably want to call a service registered in DI which talks to a database or other type of user store. You can grab your service by using the context passed into your delegates, like so ```c# services.AddAuthentication(CertificateAuthenticationDefaults.AuthenticationScheme) @@ -130,7 +111,7 @@ services.AddAuthentication(CertificateAuthenticationDefaults.AuthenticationSchem { var validationService = context.HttpContext.RequestServices.GetService(); - + if (validationService.ValidateCertificate(context.ClientCertificate)) { var claims = new[] @@ -141,17 +122,18 @@ services.AddAuthentication(CertificateAuthenticationDefaults.AuthenticationSchem context.Principal = new ClaimsPrincipal(new ClaimsIdentity(claims, context.Scheme.Name)); context.Success(); - } + } return Task.CompletedTask; } }; }); ``` + Note that conceptually the validation of the certification is an authorization concern, and putting a check on, for example, an issuer or thumbprint in an authorization policy rather than inside OnCertificateValidated() is perfectly acceptable. -## Configuring your host to require certificates +## Configuring your host to require certificates ### Kestrel @@ -170,12 +152,12 @@ public static IWebHost BuildWebHost(string[] args) }) .Build(); ``` -You must set the `ClientCertificateValidation` delegate to `CertificateValidator.DisableChannelValidation` in order to stop Kestrel using the default OS certificate validation routine and, -instead, letting the authentication handler perform the validation. + +You must set the `ClientCertificateValidation` delegate to `CertificateValidator.DisableChannelValidation` in order to stop Kestrel using the default OS certificate validation routine and, instead, letting the authentication handler perform the validation. ### IIS -In the IIS Manager +In the IIS Manager: 1. Select your Site in the Connections tab. 2. Double click the SSL Settings in the Features View window. @@ -185,9 +167,7 @@ In the IIS Manager ### Azure -See the [Azure documentation](https://docs.microsoft.com/en-us/azure/app-service/app-service-web-configure-tls-mutual-auth) -to configure Azure Web Apps then add the following to your application startup method, `Configure(IApplicationBuilder app)` add the -following line before the call to `app.UseAuthentication();` +See the [Azure documentation](https://docs.microsoft.com/azure/app-service/app-service-web-configure-tls-mutual-auth) to configure Azure Web Apps then add the following to your application startup method, `Configure(IApplicationBuilder app)` add the following line before the call to `app.UseAuthentication();`: ```c# app.UseCertificateHeaderForwarding(); @@ -195,18 +175,13 @@ app.UseCertificateHeaderForwarding(); ### Random custom web proxies -If you're using a proxy which isn't IIS or Azure's Web Apps Application Request Routing you will need to configure your proxy -to forward the certificate it received in an HTTP header. -In your application startup method, `Configure(IApplicationBuilder app)`, add the -following line before the call to `app.UseAuthentication();` +If you're using a proxy which isn't IIS or Azure's Web Apps Application Request Routing you will need to configure your proxy to forward the certificate it received in an HTTP header. In your application startup method, `Configure(IApplicationBuilder app)`, add the following line before the call to `app.UseAuthentication();`: ```c# app.UseCertificateForwarding(); ``` -You will also need to configure the Certificate Forwarding middleware to specify the header name. -In your service configuration method, `ConfigureServices(IServiceCollection services)` add -the following code to configure the header the forwarding middleware will build a certificate from; +You will also need to configure the Certificate Forwarding middleware to specify the header name. In your service configuration method, `ConfigureServices(IServiceCollection services)` add the following code to configure the header the forwarding middleware will build a certificate from: ```c# services.AddCertificateForwarding(options => @@ -215,9 +190,7 @@ services.AddCertificateForwarding(options => }); ``` -Finally, if your proxy is doing something weird to pass the header on, rather than base 64 encoding it -(looking at you nginx (╯°□°)╯︵ ┻━┻) you can override the converter option to be a func that will -perform the optional conversion, for example +Finally, if your proxy is doing something weird to pass the header on, rather than base 64 encoding it (looking at you nginx (╯°□°)╯︵ ┻━┻) you can override the converter option to be a func that will perform the optional conversion, for example ```c# services.AddCertificateForwarding(options => @@ -231,4 +204,3 @@ services.AddCertificateForwarding(options => } }); ``` - diff --git a/src/Security/Authentication/MicrosoftAccount/src/MicrosoftChallengeProperties.cs b/src/Security/Authentication/MicrosoftAccount/src/MicrosoftChallengeProperties.cs index 8625e3f093..4e9737b509 100644 --- a/src/Security/Authentication/MicrosoftAccount/src/MicrosoftChallengeProperties.cs +++ b/src/Security/Authentication/MicrosoftAccount/src/MicrosoftChallengeProperties.cs @@ -4,7 +4,7 @@ using Microsoft.AspNetCore.Authentication.OAuth; namespace Microsoft.AspNetCore.Authentication.MicrosoftAccount { /// - /// See https://docs.microsoft.com/en-us/azure/active-directory/develop/v2-oauth2-auth-code-flow#request-an-authorization-code for reference + /// See https://docs.microsoft.com/azure/active-directory/develop/v2-oauth2-auth-code-flow#request-an-authorization-code for reference /// public class MicrosoftChallengeProperties : OAuthChallengeProperties { diff --git a/src/Security/Authentication/Negotiate/test/Negotiate.FunctionalTest/CrossMachineReadMe.md b/src/Security/Authentication/Negotiate/test/Negotiate.FunctionalTest/CrossMachineReadMe.md index e263a2c5f7..ec83949331 100644 --- a/src/Security/Authentication/Negotiate/test/Negotiate.FunctionalTest/CrossMachineReadMe.md +++ b/src/Security/Authentication/Negotiate/test/Negotiate.FunctionalTest/CrossMachineReadMe.md @@ -1,24 +1,24 @@ Cross Machine Tests -Kerberos can only be tested in a multi-machine environment. On localhost it always falls back to NTLM which has different requirements. Multi-machine is also neccisary for interop testing across OSs. Kerberos also requires domain controler SPN configuration so we can't test it on arbitrary test boxes. +Kerberos can only be tested in a multi-machine environment. On localhost it always falls back to NTLM which has different requirements. Multi-machine is also necessary for interop testing across OSs. Kerberos also requires domain controller SPN configuration so we can't test it on arbitrary test boxes. Test structure: - A remote test server with various endpoints with different authentication restrictions. -- A remote test client with endpoints that execute specific scenarios. The input for these endpoints is theory data. The output is either 200Ok, or a failure code and desciption. +- A remote test client with endpoints that execute specific scenarios. The input for these endpoints is theory data. The output is either 200Ok, or a failure code and description. - The CrossMachineTest class that drives the tests. It invokes the client app with the theory data and confirms the results. -We use these three components beceause it allows us to run the tests from a dev machine or CI agent that is not part of the dedicated test domain/environment. +We use these three components because it allows us to run the tests from a dev machine or CI agent that is not part of the dedicated test domain/environment. (Static) Environment Setup: - Warning, this environment can take a day to set up. That's why we want a static test environment that we can re-use. - Create a Windows server running DNS and Active Directory. Promote it to a domain controller. - Create an SPN on this machine for Windows -> Windows testing. `setspn -S "http/chrross-dc.crkerberos.com" -U administrator` - Future: Can we replace the domain controller with an AAD instance? We'd still want a second windows machine for Windows -> Windows testing, but AAD might be easier to configure. - - https://docs.microsoft.com/en-us/azure/active-directory-domain-services/active-directory-ds-getting-started - - https://docs.microsoft.com/en-us/azure/active-directory-domain-services/active-directory-ds-join-ubuntu-linux-vm - - https://docs.microsoft.com/en-us/azure/active-directory-domain-services/active-directory-ds-enable-kcd + - https://docs.microsoft.com/azure/active-directory-domain-services/active-directory-ds-getting-started + - https://docs.microsoft.com/azure/active-directory-domain-services/active-directory-ds-join-ubuntu-linux-vm + - https://docs.microsoft.com/azure/active-directory-domain-services/active-directory-ds-enable-kcd - Create another Windows machine and join it to the test domain. -- Create a Linux machine and joing it to the domain. Ubuntu 18.04 has been used in the past. +- Create a Linux machine and joining it to the domain. Ubuntu 18.04 has been used in the past. - https://www.safesquid.com/content-filtering/integrating-linux-host-windows-ad-kerberos-sso-authentication - Include an HTTP SPN diff --git a/src/Security/README.md b/src/Security/README.md index 0ba28c1e97..5ed702c4f2 100644 --- a/src/Security/README.md +++ b/src/Security/README.md @@ -3,9 +3,9 @@ ASP.NET Core Security Contains the security and authorization middlewares for ASP.NET Core. -A list of community projects related to authentication and security for ASP.NET Core are listed in the [documentation](https://docs.microsoft.com/en-us/aspnet/core/security/authentication/community). +A list of community projects related to authentication and security for ASP.NET Core are listed in the [documentation](https://docs.microsoft.com/aspnet/core/security/authentication/community). -See the [ASP.NET Core security documentation](https://docs.microsoft.com/en-us/aspnet/core/security/). +See the [ASP.NET Core security documentation](https://docs.microsoft.com/aspnet/core/security/). ### Notes diff --git a/src/Security/samples/Identity.ExternalClaims/README.md b/src/Security/samples/Identity.ExternalClaims/README.md index 7a9141075d..70205c0367 100644 --- a/src/Security/samples/Identity.ExternalClaims/README.md +++ b/src/Security/samples/Identity.ExternalClaims/README.md @@ -4,7 +4,7 @@ AuthSamples.Identity.ExternalClaims Sample demonstrating copying over static and dynamic external claims from Google authentication during login: Steps: -1. Configure a google OAuth2 project. See https://docs.microsoft.com/en-us/aspnet/core/security/authentication/social/google-logins?tabs=aspnetcore2x for basic setup using google logins. +1. Configure a google OAuth2 project. See https://docs.microsoft.com/aspnet/core/security/authentication/social/google-logins for basic setup using google logins. 2. Update Startup.cs AddGoogle()'s options with ClientId and ClientSecret for your google app. 3. Run the app and click on the MyClaims tab, this should trigger a redirect to login. 4. Login via the Google button, this should redirect you to google. diff --git a/src/Servers/HttpSys/src/RequestProcessing/Request.cs b/src/Servers/HttpSys/src/RequestProcessing/Request.cs index 9112dfaca9..3f2fe439ba 100644 --- a/src/Servers/HttpSys/src/RequestProcessing/Request.cs +++ b/src/Servers/HttpSys/src/RequestProcessing/Request.cs @@ -259,7 +259,7 @@ namespace Microsoft.AspNetCore.Server.HttpSys Protocol = handshake.Protocol; // The OS considers client and server TLS as different enum values. SslProtocols choose to combine those for some reason. // We need to fill in the client bits so the enum shows the expected protocol. - // https://docs.microsoft.com/en-us/windows/desktop/api/schannel/ns-schannel-_secpkgcontext_connectioninfo + // https://docs.microsoft.com/windows/desktop/api/schannel/ns-schannel-_secpkgcontext_connectioninfo // Compare to https://referencesource.microsoft.com/#System/net/System/Net/SecureProtocols/_SslState.cs,8905d1bf17729de3 #pragma warning disable CS0618 // Type or member is obsolete if ((Protocol & SslProtocols.Ssl2) != 0) diff --git a/src/SignalR/README.md b/src/SignalR/README.md index 084b5fbf71..80a69f98e7 100644 --- a/src/SignalR/README.md +++ b/src/SignalR/README.md @@ -7,7 +7,7 @@ You can watch an introductory presentation here - [ASP.NET Core SignalR: Build 2 ## Documentation -Documentation for ASP.NET Core SignalR can be found in the [Real-time Apps](https://docs.microsoft.com/en-us/aspnet/core/signalr/introduction?view=aspnetcore-2.1) section of the ASP.NET Core Documentation site. +Documentation for ASP.NET Core SignalR can be found in the [Real-time Apps](https://docs.microsoft.com/aspnet/core/signalr/introduction) section of the ASP.NET Core Documentation site. ## TypeScript Version diff --git a/src/SignalR/clients/ts/signalr-protocol-msgpack/README.md b/src/SignalR/clients/ts/signalr-protocol-msgpack/README.md index e840374319..00856c5496 100644 --- a/src/SignalR/clients/ts/signalr-protocol-msgpack/README.md +++ b/src/SignalR/clients/ts/signalr-protocol-msgpack/README.md @@ -12,7 +12,7 @@ yarn add @microsoft/signalr-protocol-msgpack ## Usage -See the [SignalR Documentation](https://docs.microsoft.com/en-us/aspnet/core/signalr) at docs.microsoft.com for documentation on the latest release. [API Reference Documentation](https://docs.microsoft.com/javascript/api/%40aspnet/signalr-protocol-msgpack/?view=signalr-js-latest) is also available on docs.microsoft.com. +See the [SignalR Documentation](https://docs.microsoft.com/aspnet/core/signalr) at docs.microsoft.com for documentation on the latest release. [API Reference Documentation](https://docs.microsoft.com/javascript/api/%40aspnet/signalr-protocol-msgpack/?view=signalr-js-latest) is also available on docs.microsoft.com. ### Browser diff --git a/src/SignalR/clients/ts/signalr/README.md b/src/SignalR/clients/ts/signalr/README.md index ec8f34b227..0f1c6f705a 100644 --- a/src/SignalR/clients/ts/signalr/README.md +++ b/src/SignalR/clients/ts/signalr/README.md @@ -12,7 +12,7 @@ yarn add @microsoft/signalr ## Usage -See the [SignalR Documentation](https://docs.microsoft.com/en-us/aspnet/core/signalr) at docs.microsoft.com for documentation on the latest release. [API Reference Documentation](https://docs.microsoft.com/javascript/api/%40aspnet/signalr/?view=signalr-js-latest) is also available on docs.microsoft.com. +See the [SignalR Documentation](https://docs.microsoft.com/aspnet/core/signalr) at docs.microsoft.com for documentation on the latest release. [API Reference Documentation](https://docs.microsoft.com/javascript/api/%40aspnet/signalr/?view=signalr-js-latest) is also available on docs.microsoft.com. ### Browser diff --git a/src/SignalR/clients/ts/signalr/src/HubConnectionBuilder.ts b/src/SignalR/clients/ts/signalr/src/HubConnectionBuilder.ts index 98a23a3b3d..fa5d7432b9 100644 --- a/src/SignalR/clients/ts/signalr/src/HubConnectionBuilder.ts +++ b/src/SignalR/clients/ts/signalr/src/HubConnectionBuilder.ts @@ -70,14 +70,14 @@ export class HubConnectionBuilder { /** Configures custom logging for the {@link @microsoft/signalr.HubConnection}. * * @param {string} logLevel A string representing a LogLevel setting a minimum level of messages to log. - * See {@link https://docs.microsoft.com/en-us/aspnet/core/signalr/configuration#configure-logging|the documentation for client logging configuration} for more details. + * See {@link https://docs.microsoft.com/aspnet/core/signalr/configuration#configure-logging|the documentation for client logging configuration} for more details. */ public configureLogging(logLevel: string): HubConnectionBuilder; /** Configures custom logging for the {@link @microsoft/signalr.HubConnection}. * * @param {LogLevel | string | ILogger} logging A {@link @microsoft/signalr.LogLevel}, a string representing a LogLevel, or an object implementing the {@link @microsoft/signalr.ILogger} interface. - * See {@link https://docs.microsoft.com/en-us/aspnet/core/signalr/configuration#configure-logging|the documentation for client logging configuration} for more details. + * See {@link https://docs.microsoft.com/aspnet/core/signalr/configuration#configure-logging|the documentation for client logging configuration} for more details. * @returns The {@link @microsoft/signalr.HubConnectionBuilder} instance, for chaining. */ public configureLogging(logging: LogLevel | string | ILogger): HubConnectionBuilder; diff --git a/src/SignalR/server/StackExchangeRedis/src/Internal/RedisProtocol.cs b/src/SignalR/server/StackExchangeRedis/src/Internal/RedisProtocol.cs index a1594b0fd3..b6f276ab5e 100644 --- a/src/SignalR/server/StackExchangeRedis/src/Internal/RedisProtocol.cs +++ b/src/SignalR/server/StackExchangeRedis/src/Internal/RedisProtocol.cs @@ -28,7 +28,7 @@ namespace Microsoft.AspNetCore.SignalR.StackExchangeRedis.Internal // * Acks are sent to the Acknowledgement channel. // * See the Write[type] methods for a description of the protocol for each in-depth. // * The "Variable length integer" is the length-prefixing format used by BinaryReader/BinaryWriter: - // * https://docs.microsoft.com/en-us/dotnet/api/system.io.binarywriter.write?view=netstandard-2.0 + // * https://docs.microsoft.com/dotnet/api/system.io.binarywriter.write?view=netcore-2.2 // * The "Length prefixed string" is the string format used by BinaryReader/BinaryWriter: // * A 7-bit variable length integer encodes the length in bytes, followed by the encoded string in UTF-8. diff --git a/src/Tools/dotnet-watch/src/Internal/ProcessRunner.cs b/src/Tools/dotnet-watch/src/Internal/ProcessRunner.cs index 7874d592b6..0852eabe65 100644 --- a/src/Tools/dotnet-watch/src/Internal/ProcessRunner.cs +++ b/src/Tools/dotnet-watch/src/Internal/ProcessRunner.cs @@ -120,7 +120,7 @@ namespace Microsoft.DotNet.Watcher.Internal // this code used Process.Exited, which could result in us missing some output due to the ordering of // events. // - // See the remarks here: https://docs.microsoft.com/en-us/dotnet/api/system.diagnostics.process.waitforexit#System_Diagnostics_Process_WaitForExit_System_Int32_ + // See the remarks here: https://docs.microsoft.com/dotnet/api/system.diagnostics.process.waitforexit?view=netcore-2.2#System_Diagnostics_Process_WaitForExit_System_Int32_ if (!_process.WaitForExit(Int32.MaxValue)) { throw new TimeoutException(); From a88180472de75d22a4731812044bc58797b8ce5a Mon Sep 17 00:00:00 2001 From: Ben Adams Date: Mon, 26 Aug 2019 20:44:41 +0100 Subject: [PATCH 0180/1101] Angular-CSharp template variable naming (#13423) --- .../Controllers/OidcConfigurationController.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/ProjectTemplates/Web.Spa.ProjectTemplates/content/Angular-CSharp/Controllers/OidcConfigurationController.cs b/src/ProjectTemplates/Web.Spa.ProjectTemplates/content/Angular-CSharp/Controllers/OidcConfigurationController.cs index 75e26da4e9..cdcc89182a 100644 --- a/src/ProjectTemplates/Web.Spa.ProjectTemplates/content/Angular-CSharp/Controllers/OidcConfigurationController.cs +++ b/src/ProjectTemplates/Web.Spa.ProjectTemplates/content/Angular-CSharp/Controllers/OidcConfigurationController.cs @@ -6,12 +6,12 @@ namespace Company.WebApplication1.Controllers { public class OidcConfigurationController : Controller { - private readonly ILogger logger; + private readonly ILogger _logger; - public OidcConfigurationController(IClientRequestParametersProvider clientRequestParametersProvider, ILogger _logger) + public OidcConfigurationController(IClientRequestParametersProvider clientRequestParametersProvider, ILogger logger) { ClientRequestParametersProvider = clientRequestParametersProvider; - logger = _logger; + _logger = logger; } public IClientRequestParametersProvider ClientRequestParametersProvider { get; } From 90e89e970877a39cb048bb6f0e59551351f661c3 Mon Sep 17 00:00:00 2001 From: mgbbs Date: Mon, 26 Aug 2019 18:00:00 -0500 Subject: [PATCH 0181/1101] Mgbbs/hidden for checkbox render mode (#13014) * Added CheckBoxHiddenInputRenderMode to HtmlHelperOptions, ViewContext, and html/tag helpers Fixes #12833 --- src/Mvc/Mvc.TagHelpers/src/InputTagHelper.cs | 43 ++-- .../Mvc.TagHelpers/test/InputTagHelperTest.cs | 237 ++++++++++++++++++ ...pNetCore.Mvc.ViewFeatures.netcoreapp3.0.cs | 8 + src/Mvc/Mvc.ViewFeatures/src/HtmlHelper.cs | 14 +- .../Mvc.ViewFeatures/src/HtmlHelperOptions.cs | 5 + .../CheckBoxHiddenInputRenderMode.cs | 27 ++ .../src/Rendering/ViewContext.cs | 7 + .../test/Rendering/HtmlHelperCheckboxTest.cs | 87 +++++++ 8 files changed, 406 insertions(+), 22 deletions(-) create mode 100644 src/Mvc/Mvc.ViewFeatures/src/Rendering/CheckBoxHiddenInputRenderMode.cs diff --git a/src/Mvc/Mvc.TagHelpers/src/InputTagHelper.cs b/src/Mvc/Mvc.TagHelpers/src/InputTagHelper.cs index d3df4d963d..3ce7506e08 100644 --- a/src/Mvc/Mvc.TagHelpers/src/InputTagHelper.cs +++ b/src/Mvc/Mvc.TagHelpers/src/InputTagHelper.cs @@ -309,29 +309,32 @@ namespace Microsoft.AspNetCore.Mvc.TagHelpers "checkbox")); } - // hiddenForCheckboxTag always rendered after the returned element - var hiddenForCheckboxTag = Generator.GenerateHiddenForCheckbox(ViewContext, modelExplorer, For.Name); - if (hiddenForCheckboxTag != null) + if (ViewContext.CheckBoxHiddenInputRenderMode != CheckBoxHiddenInputRenderMode.None) { - var renderingMode = - output.TagMode == TagMode.SelfClosing ? TagRenderMode.SelfClosing : TagRenderMode.StartTag; - hiddenForCheckboxTag.TagRenderMode = renderingMode; - if (!hiddenForCheckboxTag.Attributes.ContainsKey("name") && - !string.IsNullOrEmpty(Name)) + // hiddenForCheckboxTag always rendered after the returned element + var hiddenForCheckboxTag = Generator.GenerateHiddenForCheckbox(ViewContext, modelExplorer, For.Name); + if (hiddenForCheckboxTag != null) { - // The checkbox and hidden elements should have the same name attribute value. Attributes will - // match if both are present because both have a generated value. Reach here in the special case - // where user provided a non-empty fallback name. - hiddenForCheckboxTag.MergeAttribute("name", Name); - } + var renderingMode = + output.TagMode == TagMode.SelfClosing ? TagRenderMode.SelfClosing : TagRenderMode.StartTag; + hiddenForCheckboxTag.TagRenderMode = renderingMode; + if (!hiddenForCheckboxTag.Attributes.ContainsKey("name") && + !string.IsNullOrEmpty(Name)) + { + // The checkbox and hidden elements should have the same name attribute value. Attributes will + // match if both are present because both have a generated value. Reach here in the special case + // where user provided a non-empty fallback name. + hiddenForCheckboxTag.MergeAttribute("name", Name); + } - if (ViewContext.FormContext.CanRenderAtEndOfForm) - { - ViewContext.FormContext.EndOfFormContent.Add(hiddenForCheckboxTag); - } - else - { - output.PostElement.AppendHtml(hiddenForCheckboxTag); + if (ViewContext.CheckBoxHiddenInputRenderMode == CheckBoxHiddenInputRenderMode.EndOfForm && ViewContext.FormContext.CanRenderAtEndOfForm) + { + ViewContext.FormContext.EndOfFormContent.Add(hiddenForCheckboxTag); + } + else + { + output.PostElement.AppendHtml(hiddenForCheckboxTag); + } } } diff --git a/src/Mvc/Mvc.TagHelpers/test/InputTagHelperTest.cs b/src/Mvc/Mvc.TagHelpers/test/InputTagHelperTest.cs index 265ecde1f0..557454892b 100644 --- a/src/Mvc/Mvc.TagHelpers/test/InputTagHelperTest.cs +++ b/src/Mvc/Mvc.TagHelpers/test/InputTagHelperTest.cs @@ -838,6 +838,243 @@ namespace Microsoft.AspNetCore.Mvc.TagHelpers Assert.Equal(expectedTagName, output.TagName); } + [Fact] + public async Task ProcessAsync_GenerateCheckBox_WithHiddenInputRenderModeNone() + { + var propertyName = "-expression-"; + var expectedTagName = "input"; + var inputTypeName = "checkbox"; + var expectedAttributes = new TagHelperAttributeList + { + { "name", propertyName }, + { "type", inputTypeName }, + { "value", "true" }, + }; + + var metadataProvider = new EmptyModelMetadataProvider(); + var htmlGenerator = new TestableHtmlGenerator(metadataProvider); + var model = false; + var modelExplorer = metadataProvider.GetModelExplorerForType(typeof(bool), model); + var modelExpression = new ModelExpression(name: string.Empty, modelExplorer: modelExplorer); + var viewContext = TestableHtmlGenerator.GetViewContext(model, htmlGenerator, metadataProvider); + + viewContext.CheckBoxHiddenInputRenderMode = CheckBoxHiddenInputRenderMode.None; + + var tagHelper = new InputTagHelper(htmlGenerator) + { + For = modelExpression, + InputTypeName = inputTypeName, + Name = propertyName, + ViewContext = viewContext, + }; + + var attributes = new TagHelperAttributeList + { + { "name", propertyName }, + { "type", inputTypeName }, + }; + + var context = new TagHelperContext(attributes, new Dictionary(), "test"); + var output = new TagHelperOutput( + expectedTagName, + new TagHelperAttributeList(), + getChildContentAsync: (useCachedResult, encoder) => Task.FromResult(result: null)) + { + TagMode = TagMode.SelfClosing, + }; + + // Act + await tagHelper.ProcessAsync(context, output); + + // Assert + Assert.Equal(expectedAttributes, output.Attributes); + Assert.False(output.IsContentModified); + Assert.Equal(expectedTagName, output.TagName); + + Assert.False(viewContext.FormContext.HasEndOfFormContent); + Assert.True(string.IsNullOrEmpty(HtmlContentUtilities.HtmlContentToString(output.PostElement))); + } + + [Fact] + public async Task ProcessAsync_GenerateCheckBox_WithHiddenInputRenderModeInline() + { + var propertyName = "-expression-"; + var expectedTagName = "input"; + var expectedPostElementContent = $""; + var inputTypeName = "checkbox"; + var expectedAttributes = new TagHelperAttributeList + { + { "name", propertyName }, + { "type", inputTypeName }, + { "value", "true" }, + }; + + var metadataProvider = new EmptyModelMetadataProvider(); + var htmlGenerator = new TestableHtmlGenerator(metadataProvider); + var model = false; + var modelExplorer = metadataProvider.GetModelExplorerForType(typeof(bool), model); + var modelExpression = new ModelExpression(name: string.Empty, modelExplorer: modelExplorer); + var viewContext = TestableHtmlGenerator.GetViewContext(model, htmlGenerator, metadataProvider); + + viewContext.FormContext.CanRenderAtEndOfForm = true; + viewContext.CheckBoxHiddenInputRenderMode = CheckBoxHiddenInputRenderMode.Inline; + + var tagHelper = new InputTagHelper(htmlGenerator) + { + For = modelExpression, + InputTypeName = inputTypeName, + Name = propertyName, + ViewContext = viewContext, + }; + + var attributes = new TagHelperAttributeList + { + { "name", propertyName }, + { "type", inputTypeName }, + }; + + var context = new TagHelperContext(attributes, new Dictionary(), "test"); + var output = new TagHelperOutput( + expectedTagName, + new TagHelperAttributeList(), + getChildContentAsync: (useCachedResult, encoder) => Task.FromResult(result: null)) + { + TagMode = TagMode.SelfClosing, + }; + + // Act + await tagHelper.ProcessAsync(context, output); + + // Assert + Assert.Equal(expectedAttributes, output.Attributes); + Assert.False(output.IsContentModified); + Assert.Equal(expectedTagName, output.TagName); + + Assert.False(viewContext.FormContext.HasEndOfFormContent); + Assert.Equal(expectedPostElementContent, HtmlContentUtilities.HtmlContentToString(output.PostElement)); + } + + [Fact] + public async Task ProcessAsync_GenerateCheckBox_WithHiddenInputRenderModeEndOfForm() + { + var propertyName = "-expression-"; + var expectedTagName = "input"; + var expectedEndOfFormContent = $""; + var inputTypeName = "checkbox"; + var expectedAttributes = new TagHelperAttributeList + { + { "name", propertyName }, + { "type", inputTypeName }, + { "value", "true" }, + }; + + var metadataProvider = new EmptyModelMetadataProvider(); + var htmlGenerator = new TestableHtmlGenerator(metadataProvider); + var model = false; + var modelExplorer = metadataProvider.GetModelExplorerForType(typeof(bool), model); + var modelExpression = new ModelExpression(name: string.Empty, modelExplorer: modelExplorer); + var viewContext = TestableHtmlGenerator.GetViewContext(model, htmlGenerator, metadataProvider); + + viewContext.FormContext.CanRenderAtEndOfForm = true; + viewContext.CheckBoxHiddenInputRenderMode = CheckBoxHiddenInputRenderMode.EndOfForm; + + var tagHelper = new InputTagHelper(htmlGenerator) + { + For = modelExpression, + InputTypeName = inputTypeName, + Name = propertyName, + ViewContext = viewContext, + }; + + var attributes = new TagHelperAttributeList + { + { "name", propertyName }, + { "type", inputTypeName }, + }; + + var context = new TagHelperContext(attributes, new Dictionary(), "test"); + var output = new TagHelperOutput( + expectedTagName, + new TagHelperAttributeList(), + getChildContentAsync: (useCachedResult, encoder) => Task.FromResult(result: null)) + { + TagMode = TagMode.SelfClosing, + }; + + // Act + await tagHelper.ProcessAsync(context, output); + + // Assert + Assert.Equal(expectedAttributes, output.Attributes); + Assert.False(output.IsContentModified); + Assert.Equal(expectedTagName, output.TagName); + + Assert.Equal(expectedEndOfFormContent, string.Join("", viewContext.FormContext.EndOfFormContent.Select(html => HtmlContentUtilities.HtmlContentToString(html)))); + Assert.True(string.IsNullOrEmpty(HtmlContentUtilities.HtmlContentToString(output.PostElement))); + } + + [Fact] + public async Task ProcessAsync_GenerateCheckBox_WithHiddenInputRenderModeEndOfForm_AndCanRenderAtEndOfFormNotSet() + { + var propertyName = "-expression-"; + var expectedTagName = "input"; + var expectedPostElementContent = $""; + var inputTypeName = "checkbox"; + var expectedAttributes = new TagHelperAttributeList + { + { "name", propertyName }, + { "type", inputTypeName }, + { "value", "true" }, + }; + + var metadataProvider = new EmptyModelMetadataProvider(); + var htmlGenerator = new TestableHtmlGenerator(metadataProvider); + var model = false; + var modelExplorer = metadataProvider.GetModelExplorerForType(typeof(bool), model); + var modelExpression = new ModelExpression(name: string.Empty, modelExplorer: modelExplorer); + var viewContext = TestableHtmlGenerator.GetViewContext(model, htmlGenerator, metadataProvider); + + viewContext.FormContext.CanRenderAtEndOfForm = false; + viewContext.CheckBoxHiddenInputRenderMode = CheckBoxHiddenInputRenderMode.EndOfForm; + + var tagHelper = new InputTagHelper(htmlGenerator) + { + For = modelExpression, + InputTypeName = inputTypeName, + Name = propertyName, + ViewContext = viewContext, + }; + + var attributes = new TagHelperAttributeList + { + { "name", propertyName }, + { "type", inputTypeName }, + }; + + var context = new TagHelperContext(attributes, new Dictionary(), "test"); + var output = new TagHelperOutput( + expectedTagName, + new TagHelperAttributeList(), + getChildContentAsync: (useCachedResult, encoder) => Task.FromResult(result: null)) + { + TagMode = TagMode.SelfClosing, + }; + + // Act + await tagHelper.ProcessAsync(context, output); + + // Assert + Assert.Equal(expectedAttributes, output.Attributes); + Assert.False(output.IsContentModified); + Assert.Equal(expectedTagName, output.TagName); + + Assert.False(viewContext.FormContext.HasEndOfFormContent); + Assert.Equal(expectedPostElementContent, HtmlContentUtilities.HtmlContentToString(output.PostElement)); + } + [Fact] public async Task ProcessAsync_CallsGenerateCheckBox_WithExpectedParameters() { diff --git a/src/Mvc/Mvc.ViewFeatures/ref/Microsoft.AspNetCore.Mvc.ViewFeatures.netcoreapp3.0.cs b/src/Mvc/Mvc.ViewFeatures/ref/Microsoft.AspNetCore.Mvc.ViewFeatures.netcoreapp3.0.cs index 1c78011d63..9125dacce4 100644 --- a/src/Mvc/Mvc.ViewFeatures/ref/Microsoft.AspNetCore.Mvc.ViewFeatures.netcoreapp3.0.cs +++ b/src/Mvc/Mvc.ViewFeatures/ref/Microsoft.AspNetCore.Mvc.ViewFeatures.netcoreapp3.0.cs @@ -312,6 +312,12 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding } namespace Microsoft.AspNetCore.Mvc.Rendering { + public enum CheckBoxHiddenInputRenderMode + { + None = 0, + Inline = 1, + EndOfForm = 2, + } public enum FormMethod { Get = 0, @@ -678,6 +684,7 @@ namespace Microsoft.AspNetCore.Mvc.Rendering public ViewContext() { } public ViewContext(Microsoft.AspNetCore.Mvc.ActionContext actionContext, Microsoft.AspNetCore.Mvc.ViewEngines.IView view, Microsoft.AspNetCore.Mvc.ViewFeatures.ViewDataDictionary viewData, Microsoft.AspNetCore.Mvc.ViewFeatures.ITempDataDictionary tempData, System.IO.TextWriter writer, Microsoft.AspNetCore.Mvc.ViewFeatures.HtmlHelperOptions htmlHelperOptions) { } public ViewContext(Microsoft.AspNetCore.Mvc.Rendering.ViewContext viewContext, Microsoft.AspNetCore.Mvc.ViewEngines.IView view, Microsoft.AspNetCore.Mvc.ViewFeatures.ViewDataDictionary viewData, System.IO.TextWriter writer) { } + public Microsoft.AspNetCore.Mvc.Rendering.CheckBoxHiddenInputRenderMode CheckBoxHiddenInputRenderMode { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } public bool ClientValidationEnabled { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } public string ExecutingFilePath { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } public virtual Microsoft.AspNetCore.Mvc.ViewFeatures.FormContext FormContext { get { throw null; } set { } } @@ -1062,6 +1069,7 @@ namespace Microsoft.AspNetCore.Mvc.ViewFeatures public partial class HtmlHelperOptions { public HtmlHelperOptions() { } + public Microsoft.AspNetCore.Mvc.Rendering.CheckBoxHiddenInputRenderMode CheckBoxHiddenInputRenderMode { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } public bool ClientValidationEnabled { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } public Microsoft.AspNetCore.Mvc.Rendering.Html5DateRenderingMode Html5DateRenderingMode { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } public string IdAttributeDotReplacement { get { throw null; } set { } } diff --git a/src/Mvc/Mvc.ViewFeatures/src/HtmlHelper.cs b/src/Mvc/Mvc.ViewFeatures/src/HtmlHelper.cs index 016246f4a4..cf8ee07fd9 100644 --- a/src/Mvc/Mvc.ViewFeatures/src/HtmlHelper.cs +++ b/src/Mvc/Mvc.ViewFeatures/src/HtmlHelper.cs @@ -721,8 +721,18 @@ namespace Microsoft.AspNetCore.Mvc.ViewFeatures isChecked, htmlAttributes); + if (checkbox == null) + { + return HtmlString.Empty; + } + + if (ViewContext.CheckBoxHiddenInputRenderMode == CheckBoxHiddenInputRenderMode.None) + { + return checkbox; + } + var hiddenForCheckbox = _htmlGenerator.GenerateHiddenForCheckbox(ViewContext, modelExplorer, expression); - if (checkbox == null || hiddenForCheckbox == null) + if (hiddenForCheckbox == null) { return HtmlString.Empty; } @@ -736,7 +746,7 @@ namespace Microsoft.AspNetCore.Mvc.ViewFeatures hiddenForCheckbox.MergeAttribute("name", name); } - if (ViewContext.FormContext.CanRenderAtEndOfForm) + if (ViewContext.CheckBoxHiddenInputRenderMode == CheckBoxHiddenInputRenderMode.EndOfForm && ViewContext.FormContext.CanRenderAtEndOfForm) { ViewContext.FormContext.EndOfFormContent.Add(hiddenForCheckbox); return checkbox; diff --git a/src/Mvc/Mvc.ViewFeatures/src/HtmlHelperOptions.cs b/src/Mvc/Mvc.ViewFeatures/src/HtmlHelperOptions.cs index 952bc7b4d9..f40bdf93f6 100644 --- a/src/Mvc/Mvc.ViewFeatures/src/HtmlHelperOptions.cs +++ b/src/Mvc/Mvc.ViewFeatures/src/HtmlHelperOptions.cs @@ -56,5 +56,10 @@ namespace Microsoft.AspNetCore.Mvc.ViewFeatures /// and other overloads. /// public string ValidationSummaryMessageElement { get; set; } = "span"; + + /// + /// Gets or sets the way hidden inputs are rendered for checkbox tag helpers and html helpers. + /// + public CheckBoxHiddenInputRenderMode CheckBoxHiddenInputRenderMode { get; set; } = CheckBoxHiddenInputRenderMode.EndOfForm; } } diff --git a/src/Mvc/Mvc.ViewFeatures/src/Rendering/CheckBoxHiddenInputRenderMode.cs b/src/Mvc/Mvc.ViewFeatures/src/Rendering/CheckBoxHiddenInputRenderMode.cs new file mode 100644 index 0000000000..3d89c44d80 --- /dev/null +++ b/src/Mvc/Mvc.ViewFeatures/src/Rendering/CheckBoxHiddenInputRenderMode.cs @@ -0,0 +1,27 @@ +// 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. + +namespace Microsoft.AspNetCore.Mvc.Rendering +{ + /// + /// Controls the rendering of hidden input fields when using CheckBox tag helpers or html helpers. + /// + public enum CheckBoxHiddenInputRenderMode + { + /// + /// Hidden input fields will not be automatically rendered. If checkbox is not checked, no value will be posted. + /// + None = 0, + + /// + /// Hidden input fields will be rendered inline with each checkbox. Use this for legacy ASP.NET MVC behavior. + /// + Inline = 1, + + /// + /// Hidden input fields will be rendered for each checkbox at the bottom of the form element. This is the preferred render method and default MVC behavior. + /// If is false, will fall back on . + /// + EndOfForm = 2 + } +} diff --git a/src/Mvc/Mvc.ViewFeatures/src/Rendering/ViewContext.cs b/src/Mvc/Mvc.ViewFeatures/src/Rendering/ViewContext.cs index f80bf61758..b5069c7ca6 100644 --- a/src/Mvc/Mvc.ViewFeatures/src/Rendering/ViewContext.cs +++ b/src/Mvc/Mvc.ViewFeatures/src/Rendering/ViewContext.cs @@ -87,6 +87,7 @@ namespace Microsoft.AspNetCore.Mvc.Rendering Html5DateRenderingMode = htmlHelperOptions.Html5DateRenderingMode; ValidationSummaryMessageElement = htmlHelperOptions.ValidationSummaryMessageElement; ValidationMessageElement = htmlHelperOptions.ValidationMessageElement; + CheckBoxHiddenInputRenderMode = htmlHelperOptions.CheckBoxHiddenInputRenderMode; } /// @@ -129,6 +130,7 @@ namespace Microsoft.AspNetCore.Mvc.Rendering Html5DateRenderingMode = viewContext.Html5DateRenderingMode; ValidationSummaryMessageElement = viewContext.ValidationSummaryMessageElement; ValidationMessageElement = viewContext.ValidationMessageElement; + CheckBoxHiddenInputRenderMode = viewContext.CheckBoxHiddenInputRenderMode; ExecutingFilePath = viewContext.ExecutingFilePath; View = view; @@ -180,6 +182,11 @@ namespace Microsoft.AspNetCore.Mvc.Rendering /// public string ValidationMessageElement { get; set; } + /// + /// Gets or sets the way hidden inputs are rendered for checkbox tag helpers and html helpers. + /// + public CheckBoxHiddenInputRenderMode CheckBoxHiddenInputRenderMode { get; set; } + /// /// Gets the dynamic view bag. /// diff --git a/src/Mvc/Mvc.ViewFeatures/test/Rendering/HtmlHelperCheckboxTest.cs b/src/Mvc/Mvc.ViewFeatures/test/Rendering/HtmlHelperCheckboxTest.cs index 67cfb7f64e..ab35762255 100644 --- a/src/Mvc/Mvc.ViewFeatures/test/Rendering/HtmlHelperCheckboxTest.cs +++ b/src/Mvc/Mvc.ViewFeatures/test/Rendering/HtmlHelperCheckboxTest.cs @@ -169,6 +169,93 @@ namespace Microsoft.AspNetCore.Mvc.Rendering writer.ToString()); } + [Fact] + public void CheckBox_WithHiddenInputRenderModeNone_DoesNotGenerateHiddenInput() + { + // Arrange + var requiredMessage = ValidationAttributeUtil.GetRequiredErrorMessage("Boolean"); + var expected = @""; + var helper = DefaultTemplatesUtilities.GetHtmlHelper(GetTestModelViewData()); + helper.ViewContext.CheckBoxHiddenInputRenderMode = CheckBoxHiddenInputRenderMode.None; + + // Act + var html = helper.CheckBox("Property1", isChecked: true, htmlAttributes: null); + + // Assert + Assert.False(helper.ViewContext.FormContext.HasEndOfFormContent); + Assert.Equal(expected, HtmlContentUtilities.HtmlContentToString(html)); + } + + [Fact] + public void CheckBox_WithHiddenInputRenderModeInline_GeneratesHiddenInput() + { + // Arrange + var requiredMessage = ValidationAttributeUtil.GetRequiredErrorMessage("Boolean"); + var expected = @""; + var helper = DefaultTemplatesUtilities.GetHtmlHelper(GetTestModelViewData()); + helper.ViewContext.FormContext.CanRenderAtEndOfForm = true; + helper.ViewContext.CheckBoxHiddenInputRenderMode = CheckBoxHiddenInputRenderMode.Inline; + + // Act + var html = helper.CheckBox("Property1", isChecked: true, htmlAttributes: null); + + // Assert + Assert.Equal(expected, HtmlContentUtilities.HtmlContentToString(html)); + } + + [Fact] + public void CheckBox_WithHiddenInputRenderModeEndOfForm_GeneratesHiddenInput() + { + // Arrange + var requiredMessage = ValidationAttributeUtil.GetRequiredErrorMessage("Boolean"); + var expected = @""; + var helper = DefaultTemplatesUtilities.GetHtmlHelper(GetTestModelViewData()); + helper.ViewContext.FormContext.CanRenderAtEndOfForm = true; + helper.ViewContext.CheckBoxHiddenInputRenderMode = CheckBoxHiddenInputRenderMode.EndOfForm; + + // Act + var html = helper.CheckBox("Property1", isChecked: true, htmlAttributes: null); + + // Assert + Assert.True(helper.ViewContext.FormContext.HasEndOfFormContent); + Assert.Equal(expected, HtmlContentUtilities.HtmlContentToString(html)); + var writer = new StringWriter(); + var hiddenTag = Assert.Single(helper.ViewContext.FormContext.EndOfFormContent); + hiddenTag.WriteTo(writer, new HtmlTestEncoder()); + Assert.Equal("", + writer.ToString()); + } + + [Fact] + public void CheckBox_WithHiddenInputRenderModeEndOfForm_WithCanRenderAtEndOfFormNotSet_GeneratesHiddenInput() + { + // Arrange + var requiredMessage = ValidationAttributeUtil.GetRequiredErrorMessage("Boolean"); + var expected = @""; + var helper = DefaultTemplatesUtilities.GetHtmlHelper(GetTestModelViewData()); + helper.ViewContext.FormContext.CanRenderAtEndOfForm = false; + helper.ViewContext.CheckBoxHiddenInputRenderMode = CheckBoxHiddenInputRenderMode.EndOfForm; + + // Act + var html = helper.CheckBox("Property1", isChecked: true, htmlAttributes: null); + + // Assert + Assert.False(helper.ViewContext.FormContext.HasEndOfFormContent); + Assert.Equal(expected, HtmlContentUtilities.HtmlContentToString(html)); + } + [Fact] public void CheckBoxUsesAttemptedValueFromModelState() { From a2178e50971d1f0ed9356a2b3905dc8f20cbed02 Mon Sep 17 00:00:00 2001 From: Alexej Timonin Date: Tue, 27 Aug 2019 01:43:43 +0200 Subject: [PATCH 0182/1101] Correct capitalization of JavaScript in template site.js files (#13456) Fixes #10414 --- .../content/RazorPagesWeb-CSharp/wwwroot/js/site.js | 2 +- .../content/StarterWeb-FSharp/wwwroot/js/site.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/ProjectTemplates/Web.ProjectTemplates/content/RazorPagesWeb-CSharp/wwwroot/js/site.js b/src/ProjectTemplates/Web.ProjectTemplates/content/RazorPagesWeb-CSharp/wwwroot/js/site.js index 3c76e6dc45..ac49c18641 100644 --- a/src/ProjectTemplates/Web.ProjectTemplates/content/RazorPagesWeb-CSharp/wwwroot/js/site.js +++ b/src/ProjectTemplates/Web.ProjectTemplates/content/RazorPagesWeb-CSharp/wwwroot/js/site.js @@ -1,4 +1,4 @@ // Please see documentation at https://docs.microsoft.com/aspnet/core/client-side/bundling-and-minification // for details on configuring this project to bundle and minify static web assets. -// Write your Javascript code. +// Write your JavaScript code. diff --git a/src/ProjectTemplates/Web.ProjectTemplates/content/StarterWeb-FSharp/wwwroot/js/site.js b/src/ProjectTemplates/Web.ProjectTemplates/content/StarterWeb-FSharp/wwwroot/js/site.js index 3c76e6dc45..ac49c18641 100644 --- a/src/ProjectTemplates/Web.ProjectTemplates/content/StarterWeb-FSharp/wwwroot/js/site.js +++ b/src/ProjectTemplates/Web.ProjectTemplates/content/StarterWeb-FSharp/wwwroot/js/site.js @@ -1,4 +1,4 @@ // Please see documentation at https://docs.microsoft.com/aspnet/core/client-side/bundling-and-minification // for details on configuring this project to bundle and minify static web assets. -// Write your Javascript code. +// Write your JavaScript code. From b242d5426ec1939e4dfd0dd571048516cba3b770 Mon Sep 17 00:00:00 2001 From: vbornand Date: Tue, 27 Aug 2019 18:39:41 +0200 Subject: [PATCH 0183/1101] Skip getting AzureAdOptions for not AzureADUi Cookies scheme #13311 (#13327) --- .../src/AzureADCookieOptionsConfiguration.cs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/Azure/AzureAD/Authentication.AzureAD.UI/src/AzureADCookieOptionsConfiguration.cs b/src/Azure/AzureAD/Authentication.AzureAD.UI/src/AzureADCookieOptionsConfiguration.cs index 7f4f87c8bd..6d0116921e 100644 --- a/src/Azure/AzureAD/Authentication.AzureAD.UI/src/AzureADCookieOptionsConfiguration.cs +++ b/src/Azure/AzureAD/Authentication.AzureAD.UI/src/AzureADCookieOptionsConfiguration.cs @@ -21,6 +21,11 @@ namespace Microsoft.AspNetCore.Authentication.AzureAD.UI public void Configure(string name, CookieAuthenticationOptions options) { var AzureADScheme = GetAzureADScheme(name); + if (AzureADScheme is null) + { + return; + } + var AzureADOptions = _AzureADOptions.Get(AzureADScheme); if (name != AzureADOptions.CookieSchemeName) { From af0a2048a207b49bee29de9629208afd8ba27da4 Mon Sep 17 00:00:00 2001 From: Mikael Mengistu Date: Tue, 27 Aug 2019 11:51:04 -0700 Subject: [PATCH 0184/1101] Add some more HandshakeRequest parsing tests (#13397) --- .../test/Internal/Protocol/HandshakeProtocolTests.cs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/SignalR/common/SignalR.Common/test/Internal/Protocol/HandshakeProtocolTests.cs b/src/SignalR/common/SignalR.Common/test/Internal/Protocol/HandshakeProtocolTests.cs index 27560c502a..9a88d32a57 100644 --- a/src/SignalR/common/SignalR.Common/test/Internal/Protocol/HandshakeProtocolTests.cs +++ b/src/SignalR/common/SignalR.Common/test/Internal/Protocol/HandshakeProtocolTests.cs @@ -15,6 +15,12 @@ namespace Microsoft.AspNetCore.SignalR.Common.Tests.Internal.Protocol [InlineData("{\"protocol\":\"dummy\",\"version\":1}\u001e", "dummy", 1)] [InlineData("{\"protocol\":\"\",\"version\":10}\u001e", "", 10)] [InlineData("{\"protocol\":\"\",\"version\":10,\"unknown\":null}\u001e", "", 10)] + [InlineData("{\"protocol\":\"firstProtocol\",\"protocol\":\"secondProtocol\",\"version\":1}\u001e", "secondProtocol", 1)] + [InlineData("{\"protocol\":\"firstProtocol\",\"protocol\":\"secondProtocol\",\"version\":1,\"version\":75}\u001e", "secondProtocol", 75)] + [InlineData("{\"protocol\":\"dummy\",\"version\":1,\"ignoredField\":99}\u001e", "dummy", 1)] + [InlineData("{\"protocol\":\"dummy\",\"version\":1}{\"protocol\":\"wrong\",\"version\":99}\u001e", "dummy", 1)] + [InlineData("{\"protocol\":\"\\u0064ummy\",\"version\":1}\u001e", "dummy", 1)] + [InlineData("{\"\\u0070rotoco\\u006c\":\"\\u0064ummy\",\"version\":1}\u001e", "dummy", 1)] public void ParsingHandshakeRequestMessageSuccessForValidMessages(string json, string protocol, int version) { var message = new ReadOnlySequence(Encoding.UTF8.GetBytes(json)); From e1974283f024bf2ced2a7723dc76928bd497978e Mon Sep 17 00:00:00 2001 From: Kevin Pilch Date: Fri, 23 Aug 2019 13:58:33 -0700 Subject: [PATCH 0185/1101] Add XML Docs for pubternal types \n\nCommit migrated from https://github.com/dotnet/extensions/commit/033ea04bab004a477acd6f023af171ee853965b8 --- src/FileProviders/Manifest.MSBuildTask/src/Entry.cs | 3 ++- .../Localization/src/Internal/AssemblyWrapper.cs | 6 +++++- .../Localization/src/Internal/IResourceStringProvider.cs | 8 ++++++-- .../src/Internal/ResourceManagerStringProvider.cs | 8 ++++++-- 4 files changed, 19 insertions(+), 6 deletions(-) diff --git a/src/FileProviders/Manifest.MSBuildTask/src/Entry.cs b/src/FileProviders/Manifest.MSBuildTask/src/Entry.cs index 40c815fde4..1a7f18bef8 100644 --- a/src/FileProviders/Manifest.MSBuildTask/src/Entry.cs +++ b/src/FileProviders/Manifest.MSBuildTask/src/Entry.cs @@ -8,7 +8,8 @@ using System.Diagnostics; namespace Microsoft.Extensions.FileProviders.Embedded.Manifest.Task.Internal { /// - /// This type is for internal uses only and is not meant to be consumed by any other library. + /// This API supports infrastructure and is not intended to be used + /// directly from your code. This API may change or be removed in future releases. /// [DebuggerDisplay("{Name,nq}")] public class Entry : IEquatable diff --git a/src/Localization/Localization/src/Internal/AssemblyWrapper.cs b/src/Localization/Localization/src/Internal/AssemblyWrapper.cs index b0c3c2bce1..11e118e326 100644 --- a/src/Localization/Localization/src/Internal/AssemblyWrapper.cs +++ b/src/Localization/Localization/src/Internal/AssemblyWrapper.cs @@ -1,4 +1,4 @@ -// Copyright (c) .NET Foundation. All rights reserved. +// 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; @@ -7,6 +7,10 @@ using System.Reflection; namespace Microsoft.Extensions.Localization.Internal { + /// + /// This API supports infrastructure and is not intended to be used + /// directly from your code. This API may change or be removed in future releases. + /// public class AssemblyWrapper { public AssemblyWrapper(Assembly assembly) diff --git a/src/Localization/Localization/src/Internal/IResourceStringProvider.cs b/src/Localization/Localization/src/Internal/IResourceStringProvider.cs index b74bd80eda..157e8e976e 100644 --- a/src/Localization/Localization/src/Internal/IResourceStringProvider.cs +++ b/src/Localization/Localization/src/Internal/IResourceStringProvider.cs @@ -1,11 +1,15 @@ -// 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. +// 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.Collections.Generic; using System.Globalization; namespace Microsoft.Extensions.Localization.Internal { + /// + /// This API supports infrastructure and is not intended to be used + /// directly from your code. This API may change or be removed in future releases. + /// public interface IResourceStringProvider { IList GetAllResourceStrings(CultureInfo culture, bool throwOnMissing); diff --git a/src/Localization/Localization/src/Internal/ResourceManagerStringProvider.cs b/src/Localization/Localization/src/Internal/ResourceManagerStringProvider.cs index 9eef8c84a8..62250938c8 100644 --- a/src/Localization/Localization/src/Internal/ResourceManagerStringProvider.cs +++ b/src/Localization/Localization/src/Internal/ResourceManagerStringProvider.cs @@ -1,5 +1,5 @@ -// 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. +// 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.Collections; using System.Collections.Generic; @@ -9,6 +9,10 @@ using System.Resources; namespace Microsoft.Extensions.Localization.Internal { + /// + /// This API supports infrastructure and is not intended to be used + /// directly from your code. This API may change or be removed in future releases. + /// public class ResourceManagerStringProvider : IResourceStringProvider { private readonly IResourceNamesCache _resourceNamesCache; From e9179bacd7f997a51f9619511985671e0c131a11 Mon Sep 17 00:00:00 2001 From: Pranav K Date: Tue, 27 Aug 2019 13:45:45 -0700 Subject: [PATCH 0186/1101] Add Content-Language header in localization middlewware (#13479) Fixes https://github.com/aspnet/AspNetCore/issues/11923 --- src/Middleware/Localization/Localization.slnf | 16 ++++++ ...t.AspNetCore.Localization.netcoreapp3.0.cs | 1 + .../src/RequestLocalizationMiddleware.cs | 10 +++- .../src/RequestLocalizationOptions.cs | 5 ++ .../test/FunctionalTests/LocalizationTest.cs | 13 ++++- .../StartupContentLanguageHeader.cs | 49 +++++++++++++++++++ 6 files changed, 90 insertions(+), 4 deletions(-) create mode 100644 src/Middleware/Localization/Localization.slnf create mode 100644 src/Middleware/Localization/testassets/LocalizationWebsite/StartupContentLanguageHeader.cs diff --git a/src/Middleware/Localization/Localization.slnf b/src/Middleware/Localization/Localization.slnf new file mode 100644 index 0000000000..e4494b3619 --- /dev/null +++ b/src/Middleware/Localization/Localization.slnf @@ -0,0 +1,16 @@ +{ + "solution": { + "path": "..\\Middleware.sln", + "projects": [ + "Localization.Routing\\src\\Microsoft.AspNetCore.Localization.Routing.csproj", + "Localization.Routing\\test\\Microsoft.AspNetCore.Localization.Routing.Tests.csproj", + "Localization\\sample\\LocalizationSample.csproj", + "Localization\\src\\Microsoft.AspNetCore.Localization.csproj", + "Localization\\test\\FunctionalTests\\Microsoft.AspNetCore.Localization.FunctionalTests.csproj", + "Localization\\test\\UnitTests\\Microsoft.AspNetCore.Localization.Tests.csproj", + "Localization\\testassets\\LocalizationWebsite\\LocalizationWebsite.csproj", + "Localization\\testassets\\ResourcesClassLibraryNoAttribute\\ResourcesClassLibraryNoAttribute.csproj", + "Localization\\testassets\\ResourcesClassLibraryWithAttribute\\ResourcesClassLibraryWithAttribute.csproj" + ] + } +} \ No newline at end of file diff --git a/src/Middleware/Localization/ref/Microsoft.AspNetCore.Localization.netcoreapp3.0.cs b/src/Middleware/Localization/ref/Microsoft.AspNetCore.Localization.netcoreapp3.0.cs index 599db4aca6..558921ba6b 100644 --- a/src/Middleware/Localization/ref/Microsoft.AspNetCore.Localization.netcoreapp3.0.cs +++ b/src/Middleware/Localization/ref/Microsoft.AspNetCore.Localization.netcoreapp3.0.cs @@ -13,6 +13,7 @@ namespace Microsoft.AspNetCore.Builder public partial class RequestLocalizationOptions { public RequestLocalizationOptions() { } + public bool ApplyCurrentCultureToResponseHeaders { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } public Microsoft.AspNetCore.Localization.RequestCulture DefaultRequestCulture { get { throw null; } set { } } public bool FallBackToParentCultures { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } public bool FallBackToParentUICultures { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } diff --git a/src/Middleware/Localization/src/RequestLocalizationMiddleware.cs b/src/Middleware/Localization/src/RequestLocalizationMiddleware.cs index c6ec3e1c06..07277ecdd7 100644 --- a/src/Middleware/Localization/src/RequestLocalizationMiddleware.cs +++ b/src/Middleware/Localization/src/RequestLocalizationMiddleware.cs @@ -1,5 +1,5 @@ -// 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. +// 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.Collections.Generic; @@ -13,6 +13,7 @@ using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging.Abstractions; using Microsoft.Extensions.Options; using Microsoft.Extensions.Primitives; +using Microsoft.Net.Http.Headers; namespace Microsoft.AspNetCore.Localization { @@ -146,6 +147,11 @@ namespace Microsoft.AspNetCore.Localization SetCurrentThreadCulture(requestCulture); + if (_options.ApplyCurrentCultureToResponseHeaders) + { + context.Response.Headers.Add(HeaderNames.ContentLanguage, requestCulture.UICulture.Name); + } + await _next(context); } diff --git a/src/Middleware/Localization/src/RequestLocalizationOptions.cs b/src/Middleware/Localization/src/RequestLocalizationOptions.cs index 16776364f0..95ca74fe32 100644 --- a/src/Middleware/Localization/src/RequestLocalizationOptions.cs +++ b/src/Middleware/Localization/src/RequestLocalizationOptions.cs @@ -84,6 +84,11 @@ namespace Microsoft.AspNetCore.Builder /// public bool FallBackToParentUICultures { get; set; } = true; + /// + /// Gets or sets a value that determines if is applied to the response Content-Language header. + /// + public bool ApplyCurrentCultureToResponseHeaders { get; set; } + /// /// The cultures supported by the application. The will only set /// the current request culture to an entry in this list. diff --git a/src/Middleware/Localization/test/FunctionalTests/LocalizationTest.cs b/src/Middleware/Localization/test/FunctionalTests/LocalizationTest.cs index 5f09115be2..15b00dfacf 100644 --- a/src/Middleware/Localization/test/FunctionalTests/LocalizationTest.cs +++ b/src/Middleware/Localization/test/FunctionalTests/LocalizationTest.cs @@ -1,5 +1,5 @@ -// 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. +// 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.Net; @@ -14,6 +14,15 @@ namespace Microsoft.AspNetCore.Localization.FunctionalTests { public class LocalizationTest { + [Fact] + public Task Localization_ContentLanguageHeader() + { + return RunTest( + typeof(StartupContentLanguageHeader), + "ar-YE", + "True ar-YE"); + } + [Fact] public Task Localization_CustomCulture() { diff --git a/src/Middleware/Localization/testassets/LocalizationWebsite/StartupContentLanguageHeader.cs b/src/Middleware/Localization/testassets/LocalizationWebsite/StartupContentLanguageHeader.cs new file mode 100644 index 0000000000..849743184c --- /dev/null +++ b/src/Middleware/Localization/testassets/LocalizationWebsite/StartupContentLanguageHeader.cs @@ -0,0 +1,49 @@ +// 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.Collections.Generic; +using System.Globalization; +using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Localization; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Net.Http.Headers; + +namespace LocalizationWebsite +{ + public class StartupContentLanguageHeader + { + public void ConfigureServices(IServiceCollection services) + { + services.AddLocalization(); + } + + public void Configure( + IApplicationBuilder app) + { + app.UseRequestLocalization(new RequestLocalizationOptions + { + DefaultRequestCulture = new RequestCulture("en-US"), + SupportedCultures = new List() + { + new CultureInfo("ar-YE") + }, + SupportedUICultures = new List() + { + new CultureInfo("ar-YE") + }, + ApplyCurrentCultureToResponseHeaders = true + }); + + app.Run(async (context) => + { + var hasContentLanguageHeader = context.Response.Headers.ContainsKey(HeaderNames.ContentLanguage); + var contentLanguage = context.Response.Headers[HeaderNames.ContentLanguage].ToString(); + + await context.Response.WriteAsync(hasContentLanguageHeader.ToString()); + await context.Response.WriteAsync(" "); + await context.Response.WriteAsync(contentLanguage); + }); + } + } +} \ No newline at end of file From f676c249d25eb438ffc282edb551d86eea1d9709 Mon Sep 17 00:00:00 2001 From: "dotnet-maestro[bot]" <42748379+dotnet-maestro[bot]@users.noreply.github.com> Date: Wed, 28 Aug 2019 00:21:22 +0000 Subject: [PATCH 0187/1101] [master] Update dependencies from 3 repositories (#12977) * Update dependencies from https://github.com/aspnet/EntityFrameworkCore build 20190807.3 - Microsoft.EntityFrameworkCore.Tools - 5.0.0-alpha1.19407.3 - Microsoft.EntityFrameworkCore.SqlServer - 5.0.0-alpha1.19407.3 - dotnet-ef - 5.0.0-alpha1.19407.3 - Microsoft.EntityFrameworkCore - 5.0.0-alpha1.19407.3 - Microsoft.EntityFrameworkCore.InMemory - 5.0.0-alpha1.19407.3 - Microsoft.EntityFrameworkCore.Relational - 5.0.0-alpha1.19407.3 - Microsoft.EntityFrameworkCore.Sqlite - 5.0.0-alpha1.19407.3 Dependency coherency updates - Microsoft.AspNetCore.Analyzer.Testing - 5.0.0-alpha1.19406.3 (parent: Microsoft.EntityFrameworkCore) - Microsoft.AspNetCore.BenchmarkRunner.Sources - 5.0.0-alpha1.19406.3 (parent: Microsoft.EntityFrameworkCore) - Microsoft.Extensions.ActivatorUtilities.Sources - 5.0.0-alpha1.19406.3 (parent: Microsoft.EntityFrameworkCore) - Microsoft.Extensions.Caching.Abstractions - 5.0.0-alpha1.19406.3 (parent: Microsoft.EntityFrameworkCore) - Microsoft.Extensions.Caching.Memory - 5.0.0-alpha1.19406.3 (parent: Microsoft.EntityFrameworkCore) - Microsoft.Extensions.Caching.SqlServer - 5.0.0-alpha1.19406.3 (parent: Microsoft.EntityFrameworkCore) - Microsoft.Extensions.Caching.StackExchangeRedis - 5.0.0-alpha1.19406.3 (parent: Microsoft.EntityFrameworkCore) - Microsoft.Extensions.CommandLineUtils.Sources - 5.0.0-alpha1.19406.3 (parent: Microsoft.EntityFrameworkCore) - Microsoft.Extensions.Configuration.Abstractions - 5.0.0-alpha1.19406.3 (parent: Microsoft.EntityFrameworkCore) - Microsoft.Extensions.Configuration.AzureKeyVault - 5.0.0-alpha1.19406.3 (parent: Microsoft.EntityFrameworkCore) - Microsoft.Extensions.Configuration.Binder - 5.0.0-alpha1.19406.3 (parent: Microsoft.EntityFrameworkCore) - Microsoft.Extensions.Configuration.CommandLine - 5.0.0-alpha1.19406.3 (parent: Microsoft.EntityFrameworkCore) - Microsoft.Extensions.Configuration.EnvironmentVariables - 5.0.0-alpha1.19406.3 (parent: Microsoft.EntityFrameworkCore) - Microsoft.Extensions.Configuration.FileExtensions - 5.0.0-alpha1.19406.3 (parent: Microsoft.EntityFrameworkCore) - Microsoft.Extensions.Configuration.Ini - 5.0.0-alpha1.19406.3 (parent: Microsoft.EntityFrameworkCore) - Microsoft.Extensions.Configuration.Json - 5.0.0-alpha1.19406.3 (parent: Microsoft.EntityFrameworkCore) - Microsoft.Extensions.Configuration.KeyPerFile - 5.0.0-alpha1.19406.3 (parent: Microsoft.EntityFrameworkCore) - Microsoft.Extensions.Configuration.UserSecrets - 5.0.0-alpha1.19406.3 (parent: Microsoft.EntityFrameworkCore) - Microsoft.Extensions.Configuration.Xml - 5.0.0-alpha1.19406.3 (parent: Microsoft.EntityFrameworkCore) - Microsoft.Extensions.Configuration - 5.0.0-alpha1.19406.3 (parent: Microsoft.EntityFrameworkCore) - Microsoft.Extensions.DependencyInjection.Abstractions - 5.0.0-alpha1.19406.3 (parent: Microsoft.EntityFrameworkCore) - Microsoft.Extensions.DependencyInjection - 5.0.0-alpha1.19406.3 (parent: Microsoft.EntityFrameworkCore) - Microsoft.Extensions.DiagnosticAdapter - 5.0.0-alpha1.19406.3 (parent: Microsoft.EntityFrameworkCore) - Microsoft.Extensions.Diagnostics.HealthChecks.Abstractions - 5.0.0-alpha1.19406.3 (parent: Microsoft.EntityFrameworkCore) - Microsoft.Extensions.Diagnostics.HealthChecks - 5.0.0-alpha1.19406.3 (parent: Microsoft.EntityFrameworkCore) - Microsoft.Extensions.FileProviders.Abstractions - 5.0.0-alpha1.19406.3 (parent: Microsoft.EntityFrameworkCore) - Microsoft.Extensions.FileProviders.Composite - 5.0.0-alpha1.19406.3 (parent: Microsoft.EntityFrameworkCore) - Microsoft.Extensions.FileProviders.Embedded - 5.0.0-alpha1.19406.3 (parent: Microsoft.EntityFrameworkCore) - Microsoft.Extensions.FileProviders.Physical - 5.0.0-alpha1.19406.3 (parent: Microsoft.EntityFrameworkCore) - Microsoft.Extensions.FileSystemGlobbing - 5.0.0-alpha1.19406.3 (parent: Microsoft.EntityFrameworkCore) - Microsoft.Extensions.HashCodeCombiner.Sources - 5.0.0-alpha1.19406.3 (parent: Microsoft.EntityFrameworkCore) - Microsoft.Extensions.Hosting.Abstractions - 5.0.0-alpha1.19406.3 (parent: Microsoft.EntityFrameworkCore) - Microsoft.Extensions.Hosting - 5.0.0-alpha1.19406.3 (parent: Microsoft.EntityFrameworkCore) - Microsoft.Extensions.HostFactoryResolver.Sources - 5.0.0-alpha1.19406.3 (parent: Microsoft.EntityFrameworkCore) - Microsoft.Extensions.Http - 5.0.0-alpha1.19406.3 (parent: Microsoft.EntityFrameworkCore) - Microsoft.Extensions.Localization.Abstractions - 5.0.0-alpha1.19406.3 (parent: Microsoft.EntityFrameworkCore) - Microsoft.Extensions.Localization - 5.0.0-alpha1.19406.3 (parent: Microsoft.EntityFrameworkCore) - Microsoft.Extensions.Logging.Abstractions - 5.0.0-alpha1.19406.3 (parent: Microsoft.EntityFrameworkCore) - Microsoft.Extensions.Logging.AzureAppServices - 5.0.0-alpha1.19406.3 (parent: Microsoft.EntityFrameworkCore) - Microsoft.Extensions.Logging.Configuration - 5.0.0-alpha1.19406.3 (parent: Microsoft.EntityFrameworkCore) - Microsoft.Extensions.Logging.Console - 5.0.0-alpha1.19406.3 (parent: Microsoft.EntityFrameworkCore) - Microsoft.Extensions.Logging.Debug - 5.0.0-alpha1.19406.3 (parent: Microsoft.EntityFrameworkCore) - Microsoft.Extensions.Logging.EventSource - 5.0.0-alpha1.19406.3 (parent: Microsoft.EntityFrameworkCore) - Microsoft.Extensions.Logging.EventLog - 5.0.0-alpha1.19406.3 (parent: Microsoft.EntityFrameworkCore) - Microsoft.Extensions.Logging.TraceSource - 5.0.0-alpha1.19406.3 (parent: Microsoft.EntityFrameworkCore) - Microsoft.Extensions.Logging.Testing - 5.0.0-alpha1.19406.3 (parent: Microsoft.EntityFrameworkCore) - Microsoft.Extensions.ObjectPool - 5.0.0-alpha1.19406.3 (parent: Microsoft.EntityFrameworkCore) - Microsoft.Extensions.Options.ConfigurationExtensions - 5.0.0-alpha1.19406.3 (parent: Microsoft.EntityFrameworkCore) - Microsoft.Extensions.Options.DataAnnotations - 5.0.0-alpha1.19406.3 (parent: Microsoft.EntityFrameworkCore) - Microsoft.Extensions.Options - 5.0.0-alpha1.19406.3 (parent: Microsoft.EntityFrameworkCore) - Microsoft.Extensions.ParameterDefaultValue.Sources - 5.0.0-alpha1.19406.3 (parent: Microsoft.EntityFrameworkCore) - Microsoft.Extensions.Primitives - 5.0.0-alpha1.19406.3 (parent: Microsoft.EntityFrameworkCore) - Microsoft.Extensions.TypeNameHelper.Sources - 5.0.0-alpha1.19406.3 (parent: Microsoft.EntityFrameworkCore) - Microsoft.Extensions.ValueStopwatch.Sources - 5.0.0-alpha1.19406.3 (parent: Microsoft.EntityFrameworkCore) - Microsoft.Extensions.WebEncoders - 5.0.0-alpha1.19406.3 (parent: Microsoft.EntityFrameworkCore) - Microsoft.Internal.Extensions.Refs - 5.0.0-alpha1.19406.3 (parent: Microsoft.EntityFrameworkCore) - Microsoft.JSInterop - 5.0.0-alpha1.19406.3 (parent: Microsoft.EntityFrameworkCore) - Mono.WebAssembly.Interop - 5.0.0-alpha1.19406.3 (parent: Microsoft.EntityFrameworkCore) - Microsoft.Bcl.AsyncInterfaces - 1.1.0-alpha1.19381.2 (parent: Microsoft.NETCore.App.Runtime.win-x64) - Microsoft.NETCore.App.Runtime.win-x64 - 5.0.0-alpha1.19404.5 (parent: Microsoft.Extensions.Logging) - Microsoft.Extensions.Logging - 5.0.0-alpha1.19406.3 (parent: Microsoft.EntityFrameworkCore) - Microsoft.CSharp - 5.0.0-alpha1.19381.2 (parent: Microsoft.NETCore.App.Runtime.win-x64) - Microsoft.Win32.Registry - 5.0.0-alpha1.19381.2 (parent: Microsoft.NETCore.App.Runtime.win-x64) - Microsoft.Win32.SystemEvents - 5.0.0-alpha1.19381.2 (parent: Microsoft.NETCore.App.Runtime.win-x64) - System.ComponentModel.Annotations - 5.0.0-alpha1.19381.2 (parent: Microsoft.NETCore.App.Runtime.win-x64) - System.Diagnostics.EventLog - 5.0.0-alpha1.19381.2 (parent: Microsoft.NETCore.App.Runtime.win-x64) - System.Drawing.Common - 5.0.0-alpha1.19381.2 (parent: Microsoft.NETCore.App.Runtime.win-x64) - System.IO.Pipelines - 5.0.0-alpha1.19381.2 (parent: Microsoft.NETCore.App.Runtime.win-x64) - System.Net.Http.WinHttpHandler - 5.0.0-alpha1.19381.2 (parent: Microsoft.NETCore.App.Runtime.win-x64) - System.Net.WebSockets.WebSocketProtocol - 5.0.0-alpha1.19381.2 (parent: Microsoft.NETCore.App.Runtime.win-x64) - System.Reflection.Metadata - 1.8.0-alpha1.19381.2 (parent: Microsoft.NETCore.App.Runtime.win-x64) - System.Runtime.CompilerServices.Unsafe - 5.0.0-alpha1.19381.2 (parent: Microsoft.NETCore.App.Runtime.win-x64) - System.Security.Cryptography.Cng - 5.0.0-alpha1.19381.2 (parent: Microsoft.NETCore.App.Runtime.win-x64) - System.Security.Cryptography.Pkcs - 5.0.0-alpha1.19381.2 (parent: Microsoft.NETCore.App.Runtime.win-x64) - System.Security.Cryptography.Xml - 5.0.0-alpha1.19381.2 (parent: Microsoft.NETCore.App.Runtime.win-x64) - System.Security.Permissions - 5.0.0-alpha1.19381.2 (parent: Microsoft.NETCore.App.Runtime.win-x64) - System.Security.Principal.Windows - 5.0.0-alpha1.19381.2 (parent: Microsoft.NETCore.App.Runtime.win-x64) - System.ServiceProcess.ServiceController - 5.0.0-alpha1.19381.2 (parent: Microsoft.NETCore.App.Runtime.win-x64) - System.Text.Encodings.Web - 5.0.0-alpha1.19381.2 (parent: Microsoft.NETCore.App.Runtime.win-x64) - System.Text.Json - 5.0.0-alpha1.19381.2 (parent: Microsoft.NETCore.App.Runtime.win-x64) - System.Threading.Channels - 5.0.0-alpha1.19381.2 (parent: Microsoft.NETCore.App.Runtime.win-x64) - System.Windows.Extensions - 5.0.0-alpha1.19381.2 (parent: Microsoft.NETCore.App.Runtime.win-x64) - Microsoft.Extensions.DependencyModel - 5.0.0-alpha1.19404.5 (parent: Microsoft.Extensions.Logging) - Microsoft.NETCore.App.Ref - 5.0.0-alpha1.19404.5 (parent: Microsoft.Extensions.Logging) - NETStandard.Library.Ref - 2.1.0-alpha1.19404.5 (parent: Microsoft.Extensions.Logging) - Microsoft.NETCore.Platforms - 5.0.0-alpha1.19381.2 (parent: Microsoft.NETCore.App.Runtime.win-x64) - Internal.AspNetCore.Analyzers - 5.0.0-alpha1.19406.3 (parent: Microsoft.EntityFrameworkCore) - Microsoft.AspNetCore.Testing - 5.0.0-alpha1.19406.3 (parent: Microsoft.EntityFrameworkCore) - Microsoft.Net.Compilers.Toolset - 3.3.0-beta3-19406-05 (parent: Microsoft.Extensions.Logging) * First pass at fixing netcoreapp5.0 errors * Add NETCoreAppMaximumVersion, remove BundledNETCorePlatformsPackageVersion * Apply KnownFrameworkReference Workaround * Update dependencies from https://github.com/aspnet/AspNetCore-Tooling build 20190808.17 - Microsoft.NET.Sdk.Razor - 5.0.0-alpha1.19408.17 - Microsoft.CodeAnalysis.Razor - 5.0.0-alpha1.19408.17 - Microsoft.AspNetCore.Razor.Language - 5.0.0-alpha1.19408.17 - Microsoft.AspNetCore.Mvc.Razor.Extensions - 5.0.0-alpha1.19408.17 * Update TFMs to 5.0 * Retarget TFMs to netcoreapp5.0 * Change compile items from netcoreapp3.0.cs to netcoreapp5.0.cs * Rename netcoreapp3.0.cs files to netcoreapp5.0.cs * Update TargetFrameworks, Conditions * Fix missed items * Clean up remaining items in src * Fix missed merge conflict * Set TFMNetCoreMajor/MinorVersions to 5.0 * Rename missed .cs file, update ifdefs * Rename missed .cs file, update ifdefs * Update dependencies from https://github.com/aspnet/AspNetCore-Tooling build 20190809.3 - Microsoft.NET.Sdk.Razor - 5.0.0-alpha1.19409.3 - Microsoft.CodeAnalysis.Razor - 5.0.0-alpha1.19409.3 - Microsoft.AspNetCore.Razor.Language - 5.0.0-alpha1.19409.3 - Microsoft.AspNetCore.Mvc.Razor.Extensions - 5.0.0-alpha1.19409.3 * Update dependencies from https://github.com/aspnet/AspNetCore-Tooling build 20190810.1 - Microsoft.NET.Sdk.Razor - 5.0.0-alpha1.19410.1 - Microsoft.CodeAnalysis.Razor - 5.0.0-alpha1.19410.1 - Microsoft.AspNetCore.Razor.Language - 5.0.0-alpha1.19410.1 - Microsoft.AspNetCore.Mvc.Razor.Extensions - 5.0.0-alpha1.19410.1 * Use default value for 5.0 TFM * NETCOREAPP5_0 -> NETCOREAPP * attempt to fix npm build * Update TFM reverted by merge * Explictly reference netcoreapp5.0 in repotasks.tasks * Regenerate ref assemblies * Consolidate FrameworkReference workarounds * Add workaround back to d.b.t * Regen ref assemblies * regen blazor.server.js * Update dependencies from https://github.com/aspnet/AspNetCore-Tooling build 20190813.2 - Microsoft.NET.Sdk.Razor - 5.0.0-alpha1.19413.2 - Microsoft.CodeAnalysis.Razor - 5.0.0-alpha1.19413.2 - Microsoft.AspNetCore.Razor.Language - 5.0.0-alpha1.19413.2 - Microsoft.AspNetCore.Mvc.Razor.Extensions - 5.0.0-alpha1.19413.2 * Update dependencies from https://github.com/aspnet/Blazor build 20190813.1 - Microsoft.AspNetCore.Blazor.Mono - 5.0.0-alpha1.19413.1 * Exclude blazor.server.js from code check * start fixing tests * Bump test TFMs to 5.0 * Regen ref assemblies, hardcode TFM in dotnet-watch tests * Regenerate SpaServices.Extensions.netcoreapp.5.0.cs * Fix dotnet-watch csproj TFMs * Fixup 3.0 -> 5.0 files from merge * Fixup another set of 3.0 -> 5.0 misses * More 3.0 -> 5,9 * More 3.0 -> 5.0 madness * Set TargetFramework for test templates * Disable warning in ApiAuth tests * Put TFM in right place * Hardcode TFM * Update dependencies from https://github.com/aspnet/AspNetCore-Tooling build 20190814.4 - Microsoft.NET.Sdk.Razor - 5.0.0-alpha1.19414.4 - Microsoft.CodeAnalysis.Razor - 5.0.0-alpha1.19414.4 - Microsoft.AspNetCore.Razor.Language - 5.0.0-alpha1.19414.4 - Microsoft.AspNetCore.Mvc.Razor.Extensions - 5.0.0-alpha1.19414.4 * Regen ref assemblies * Hardcode TFM for template tests * Update dependencies from https://github.com/aspnet/AspNetCore-Tooling build 20190815.9 - Microsoft.NET.Sdk.Razor - 5.0.0-alpha1.19415.9 - Microsoft.CodeAnalysis.Razor - 5.0.0-alpha1.19415.9 - Microsoft.AspNetCore.Razor.Language - 5.0.0-alpha1.19415.9 - Microsoft.AspNetCore.Mvc.Razor.Extensions - 5.0.0-alpha1.19415.9 * Apply AppHostPack/FrameworkRef workaround to dotnet-watch tests * Set KnownAppHost/KnownFramework after sdk.targets import * Update dependencies from https://github.com/aspnet/AspNetCore-Tooling build 20190816.3 - Microsoft.NET.Sdk.Razor - 5.0.0-alpha1.19416.3 - Microsoft.CodeAnalysis.Razor - 5.0.0-alpha1.19416.3 - Microsoft.AspNetCore.Razor.Language - 5.0.0-alpha1.19416.3 - Microsoft.AspNetCore.Mvc.Razor.Extensions - 5.0.0-alpha1.19416.3 * Update dependencies from https://github.com/aspnet/Blazor build 20190816.2 - Microsoft.AspNetCore.Blazor.Mono - 5.0.0-alpha1.19416.2 * More workaround * Try workaround for template tests * Update to 5.0 SDK * Remove AppHostPack workaround * Remove more workarounds * Try updating RazorLangVersion * Try adding FrameworkReference workaround back * Remove framework and revert RazorLang to 3.0 * Update dependencies from https://github.com/aspnet/AspNetCore-Tooling build 20190819.4 - Microsoft.NET.Sdk.Razor - 5.0.0-alpha1.19419.4 - Microsoft.CodeAnalysis.Razor - 5.0.0-alpha1.19419.4 - Microsoft.AspNetCore.Razor.Language - 5.0.0-alpha1.19419.4 - Microsoft.AspNetCore.Mvc.Razor.Extensions - 5.0.0-alpha1.19419.4 * Update dependencies from https://github.com/aspnet/Blazor build 20190819.1 - Microsoft.AspNetCore.Blazor.Mono - 5.0.0-alpha1.19419.1 * Set env var * Add 1 workaround back for SourceBuild * Remove workaround from Project Template tests * Add back maxVersion workaround for sourcebuild * One more source build workaround * Update dependencies from https://github.com/aspnet/AspNetCore-Tooling build 20190821.3 - Microsoft.NET.Sdk.Razor - 5.0.0-alpha1.19421.3 - Microsoft.CodeAnalysis.Razor - 5.0.0-alpha1.19421.3 - Microsoft.AspNetCore.Razor.Language - 5.0.0-alpha1.19421.3 - Microsoft.AspNetCore.Mvc.Razor.Extensions - 5.0.0-alpha1.19421.3 * Update dependencies from https://github.com/aspnet/AspNetCore-Tooling build 20190822.9 - Microsoft.NET.Sdk.Razor - 5.0.0-alpha1.19422.9 - Microsoft.CodeAnalysis.Razor - 5.0.0-alpha1.19422.9 - Microsoft.AspNetCore.Razor.Language - 5.0.0-alpha1.19422.9 - Microsoft.AspNetCore.Mvc.Razor.Extensions - 5.0.0-alpha1.19422.9 * Remove workaround for Razor 3.0 SDK * Fix Blazor SDK error in SourceBuild * Update dependencies from https://github.com/aspnet/AspNetCore-Tooling build 20190822.15 - Microsoft.NET.Sdk.Razor - 5.0.0-alpha1.19422.15 - Microsoft.CodeAnalysis.Razor - 5.0.0-alpha1.19422.15 - Microsoft.AspNetCore.Razor.Language - 5.0.0-alpha1.19422.15 - Microsoft.AspNetCore.Mvc.Razor.Extensions - 5.0.0-alpha1.19422.15 * Update dependencies from https://github.com/aspnet/Blazor build 20190822.1 - Microsoft.AspNetCore.Blazor.Mono - 5.0.0-alpha1.19422.1 * Update dependencies from https://github.com/aspnet/AspNetCore-Tooling build 20190824.6 - Microsoft.NET.Sdk.Razor - 5.0.0-alpha1.19424.6 - Microsoft.CodeAnalysis.Razor - 5.0.0-alpha1.19424.6 - Microsoft.AspNetCore.Razor.Language - 5.0.0-alpha1.19424.6 - Microsoft.AspNetCore.Mvc.Razor.Extensions - 5.0.0-alpha1.19424.6 * Update dependencies from https://github.com/aspnet/AspNetCore-Tooling build 20190825.1 - Microsoft.NET.Sdk.Razor - 5.0.0-alpha1.19425.1 - Microsoft.CodeAnalysis.Razor - 5.0.0-alpha1.19425.1 - Microsoft.AspNetCore.Razor.Language - 5.0.0-alpha1.19425.1 - Microsoft.AspNetCore.Mvc.Razor.Extensions - 5.0.0-alpha1.19425.1 * Rename netcoreapp5.0.cs files to netcoreapp.cs * Fix inverted condition for Razor projects * Apply Razor logic consistently * Catch netcoreapp2.0.cs file in codecheck * Remove ref:suppressField * Reenable workarounds for Razor projects * Update dependencies from https://github.com/aspnet/AspNetCore-Tooling build 20190826.2 - Microsoft.NET.Sdk.Razor - 5.0.0-alpha1.19426.2 - Microsoft.CodeAnalysis.Razor - 5.0.0-alpha1.19426.2 - Microsoft.AspNetCore.Razor.Language - 5.0.0-alpha1.19426.2 - Microsoft.AspNetCore.Mvc.Razor.Extensions - 5.0.0-alpha1.19426.2 * Update dependencies from https://github.com/aspnet/Blazor build 20190826.2 - Microsoft.AspNetCore.Blazor.Mono - 5.0.0-alpha1.19426.2 * React to ref:suppressField breaking changes * Stop using custom version of SDK in source build * Remove SourceBuild workarounds * Skip template tests --- .azure/pipelines/ci.yml | 10 - Directory.Build.props | 2 +- eng/PublishSymbols.proj | 2 +- eng/SharedFramework.External.props | 2 +- eng/Version.Details.xml | 404 +++++++++--------- eng/Versions.props | 207 +++++---- eng/Workarounds.targets | 5 - eng/common/performance/performance-setup.ps1 | 2 +- eng/common/performance/performance-setup.sh | 2 +- eng/scripts/CodeCheck.ps1 | 1 - eng/scripts/ci-source-build.sh | 21 - eng/targets/ReferenceAssembly.targets | 5 +- .../BaselineGenerator.csproj | 2 +- eng/tools/Maestro/Maestro.csproj | 2 +- eng/tools/RepoTasks/RepoTasks.csproj | 4 +- eng/tools/RepoTasks/RepoTasks.tasks | 2 +- global.json | 4 +- ...Microsoft.AspNetCore.Analyzers.Test.csproj | 2 +- .../Microsoft.AspNetCore.Antiforgery.csproj | 6 +- ...soft.AspNetCore.Antiforgery.netcoreapp.cs} | 0 .../Microsoft.AspNetCore.Antiforgery.csproj | 2 +- ...crosoft.AspNetCore.Antiforgery.Test.csproj | 2 +- ...spNetCore.Authentication.AzureAD.UI.csproj | 6 +- ...e.Authentication.AzureAD.UI.netcoreapp.cs} | 0 ...spNetCore.Authentication.AzureAD.UI.csproj | 2 +- ...Core.Authentication.AzureAD.UI.Test.csproj | 2 +- ...etCore.Authentication.AzureADB2C.UI.csproj | 6 +- ...uthentication.AzureADB2C.UI.netcoreapp.cs} | 0 ...etCore.Authentication.AzureADB2C.UI.csproj | 2 +- ...e.Authentication.AzureADB2C.UI.Test.csproj | 2 +- .../AzureADB2CSample/AzureADB2CSample.csproj | 2 +- .../AzureADSample/AzureADSample.csproj | 2 +- ...hentication.AzureAD.FunctionalTests.csproj | 4 +- .../AzureAD.WebSite/AzureAD.WebSite.csproj | 2 +- ...ore.AzureAppServices.HostingStartup.csproj | 6 +- ...eAppServices.HostingStartup.netcoreapp.cs} | 0 ...ore.AzureAppServices.HostingStartup.csproj | 2 +- ...NetCore.AzureAppServicesIntegration.csproj | 6 +- ...AzureAppServicesIntegration.netcoreapp.cs} | 0 ...NetCore.AzureAppServicesIntegration.csproj | 2 +- ...e.AzureAppServicesIntegration.Tests.csproj | 2 +- ...zureAppServicesHostingStartupSample.csproj | 2 +- .../AzureAppServicesSample.csproj | 2 +- ...pNetCore.Components.Analyzers.Tests.csproj | 2 +- ...AspNetCore.Components.Authorization.csproj | 6 +- ...re.Components.Authorization.netcoreapp.cs} | 2 +- ...Components.Authorization.netstandard2.0.cs | 2 +- ...AspNetCore.Components.Authorization.csproj | 4 +- ...Core.Components.Authorization.Tests.csproj | 2 +- .../Microsoft.AspNetCore.Blazor.Tests.csproj | 2 +- .../Microsoft.AspNetCore.Blazor.Build.csproj | 2 +- .../Build/src/ReferenceFromSource.props | 2 +- .../GenericComponentRazorIntegrationTest.cs | 6 +- ...osoft.AspNetCore.Blazor.Build.Tests.csproj | 2 +- ...crosoft.AspNetCore.Blazor.DevServer.csproj | 2 +- ....AspNetCore.Blazor.HttpClient.Tests.csproj | 2 +- .../Microsoft.AspNetCore.Blazor.Server.csproj | 6 +- ...ft.AspNetCore.Blazor.Server.netcoreapp.cs} | 0 .../Microsoft.AspNetCore.Blazor.Server.csproj | 2 +- .../.template.config.src/template.json | 8 +- .../Server/BlazorWasm-CSharp.Server.csproj | 2 +- .../HostedInAspNet.Server.csproj | 2 +- .../testassets/MonoSanity/MonoSanity.csproj | 2 +- ...t.AspNetCore.Components.Performance.csproj | 2 +- .../Microsoft.AspNetCore.Components.csproj | 6 +- ...osoft.AspNetCore.Components.netcoreapp.cs} | 0 .../Microsoft.AspNetCore.Components.csproj | 6 +- ...t.AspNetCore.Components.multitarget.nuspec | 2 +- ...spNetCore.Components.netcoreapp5.0.nuspec} | 2 +- ...crosoft.AspNetCore.Components.Tests.csproj | 2 +- src/Components/Directory.Build.props | 2 +- ...crosoft.AspNetCore.Components.Forms.csproj | 6 +- ...AspNetCore.Components.Forms.netcoreapp.cs} | 0 ...crosoft.AspNetCore.Components.Forms.csproj | 4 +- ...t.AspNetCore.Components.Forms.Tests.csproj | 2 +- ...rosoft.AspNetCore.Components.Server.csproj | 6 +- ...spNetCore.Components.Server.netcoreapp.cs} | 0 ...rosoft.AspNetCore.Components.Server.csproj | 2 +- ....AspNetCore.Components.Server.Tests.csproj | 2 +- .../Web.JS/dist/Release/blazor.server.js | 2 +- ...Microsoft.AspNetCore.Components.Web.csproj | 6 +- ...t.AspNetCore.Components.Web.netcoreapp.cs} | 0 ...Microsoft.AspNetCore.Components.Web.csproj | 4 +- ...oft.AspNetCore.Components.Web.Tests.csproj | 2 +- ...soft.AspNetCore.Components.E2ETests.csproj | 2 +- .../test/Ignitor.Test/Ignitor.Test.csproj | 2 +- .../AfterRenderInteropComponent.razor | 2 +- .../BasicTestApp/ComponentRefComponent.razor | 2 +- .../BasicTestApp/ElementRefComponent.razor | 2 +- .../InteropOnInitializationComponent.razor | 2 +- .../ComponentsApp.Server.csproj | 2 +- .../test/testassets/Ignitor/Ignitor.csproj | 2 +- .../TestServer/Components.TestServer.csproj | 2 +- ...e.DataProtection.Abstractions.Tests.csproj | 2 +- ....DataProtection.AzureKeyVault.Tests.csproj | 2 +- ...e.DataProtection.AzureStorage.Tests.csproj | 2 +- ...NetCore.Cryptography.Internal.Tests.csproj | 2 +- ...pNetCore.Cryptography.KeyDerivation.csproj | 2 +- ....Cryptography.KeyDerivation.netcoreapp.cs} | 0 ...re.Cryptography.KeyDerivation.Tests.csproj | 2 +- ...Microsoft.AspNetCore.DataProtection.csproj | 6 +- ...t.AspNetCore.DataProtection.netcoreapp.cs} | 0 ...Microsoft.AspNetCore.DataProtection.csproj | 4 +- ...oft.AspNetCore.DataProtection.Tests.csproj | 2 +- ...Protection.EntityFrameworkCore.Test.csproj | 2 +- ...spNetCore.DataProtection.Extensions.csproj | 6 +- ...e.DataProtection.Extensions.netcoreapp.cs} | 0 ...spNetCore.DataProtection.Extensions.csproj | 4 +- ...ore.DataProtection.Extensions.Tests.csproj | 2 +- ...Protection.StackExchangeRedis.Tests.csproj | 2 +- .../samples/AzureBlob/AzureBlob.csproj | 2 +- .../AzureKeyVault/AzureKeyVault.csproj | 2 +- .../CustomEncryptorSample.csproj | 2 +- .../EntityFrameworkCoreSample.csproj | 2 +- .../KeyManagementSample.csproj | 2 +- .../samples/NonDISample/NonDISample.csproj | 2 +- src/DataProtection/samples/Redis/Redis.csproj | 2 +- .../ref/Microsoft.AspNetCore.csproj | 6 +- ....cs => Microsoft.AspNetCore.netcoreapp.cs} | 0 .../SampleApp/DefaultBuilder.SampleApp.csproj | 2 +- .../src/Microsoft.AspNetCore.csproj | 2 +- ...icrosoft.AspNetCore.FunctionalTests.csproj | 2 +- .../WebHostFunctionalTests.cs | 4 +- .../Microsoft.AspNetCore.Tests.csproj | 2 +- .../CreateDefaultBuilderApp.csproj | 2 +- .../CreateDefaultBuilderOfTApp.csproj | 2 +- .../DependencyInjectionApp.csproj | 2 +- .../StartRequestDelegateUrlApp.csproj | 2 +- .../StartRouteBuilderUrlApp.csproj | 2 +- .../StartWithIApplicationBuilderUrlApp.csproj | 2 +- ...icrosoft.AspNetCore.JsonPatch.Tests.csproj | 2 +- .../Microsoft.AspNetCore.App.Runtime.csproj | 2 +- .../Microsoft.AspNetCore.App.UnitTests.csproj | 2 +- ...oft.AspNetCore.Hosting.Abstractions.csproj | 6 +- ...etCore.Hosting.Abstractions.netcoreapp.cs} | 0 ...oft.AspNetCore.Hosting.Abstractions.csproj | 2 +- .../ref/Microsoft.AspNetCore.Hosting.csproj | 6 +- ...icrosoft.AspNetCore.Hosting.netcoreapp.cs} | 0 .../src/Microsoft.AspNetCore.Hosting.csproj | 2 +- .../Microsoft.AspNetCore.Hosting.Tests.csproj | 2 +- ...NetCore.Hosting.Server.Abstractions.csproj | 6 +- ...Hosting.Server.Abstractions.netcoreapp.cs} | 0 ...NetCore.Hosting.Server.Abstractions.csproj | 2 +- ...spNetCore.Server.IntegrationTesting.csproj | 6 +- ...e.Server.IntegrationTesting.netcoreapp.cs} | 1 + .../src/Common/Tfm.cs | 1 + ...spNetCore.Server.IntegrationTesting.csproj | 2 +- .../ref/Microsoft.AspNetCore.TestHost.csproj | 6 +- ...crosoft.AspNetCore.TestHost.netcoreapp.cs} | 0 .../src/Microsoft.AspNetCore.TestHost.csproj | 2 +- ...Microsoft.AspNetCore.TestHost.Tests.csproj | 2 +- ....AspNetCore.Hosting.WindowsServices.csproj | 6 +- ...ore.Hosting.WindowsServices.netcoreapp.cs} | 0 ....AspNetCore.Hosting.WindowsServices.csproj | 2 +- ...tCore.Hosting.WindowsServices.Tests.csproj | 2 +- .../GenericWebHost/GenericWebHost.csproj | 2 +- .../SampleStartups/SampleStartups.csproj | 2 +- ....AspNetCore.Hosting.FunctionalTests.csproj | 2 +- .../test/FunctionalTests/ShutdownTests.cs | 2 +- .../FunctionalTests/WebHostBuilderTests.cs | 2 +- .../IStartupInjectionAssemblyName.csproj | 2 +- ...rosoft.AspNetCore.Hosting.TestSites.csproj | 2 +- .../TestStartupAssembly1.csproj | 2 +- ...rosoft.AspNetCore.Html.Abstractions.csproj | 6 +- ...spNetCore.Html.Abstractions.netcoreapp.cs} | 0 ...rosoft.AspNetCore.Html.Abstractions.csproj | 2 +- ....AspNetCore.Html.Abstractions.Tests.csproj | 2 +- ...NetCore.Authentication.Abstractions.csproj | 6 +- ...Authentication.Abstractions.netcoreapp.cs} | 0 ...NetCore.Authentication.Abstractions.csproj | 2 +- ...soft.AspNetCore.Authentication.Core.csproj | 6 +- ...NetCore.Authentication.Core.netcoreapp.cs} | 0 ...soft.AspNetCore.Authentication.Core.csproj | 2 +- ...AspNetCore.Authentication.Core.Test.csproj | 2 +- .../ref/Microsoft.Net.Http.Headers.csproj | 6 +- ... Microsoft.Net.Http.Headers.netcoreapp.cs} | 0 .../src/Microsoft.Net.Http.Headers.csproj | 2 +- .../Microsoft.Net.Http.Headers.Tests.csproj | 2 +- ...rosoft.AspNetCore.Http.Abstractions.csproj | 6 +- ...spNetCore.Http.Abstractions.netcoreapp.cs} | 0 ...rosoft.AspNetCore.Http.Abstractions.csproj | 2 +- ....AspNetCore.Http.Abstractions.Tests.csproj | 2 +- ...icrosoft.AspNetCore.Http.Extensions.csproj | 6 +- ....AspNetCore.Http.Extensions.netcoreapp.cs} | 0 ...icrosoft.AspNetCore.Http.Extensions.csproj | 2 +- ...ft.AspNetCore.Http.Extensions.Tests.csproj | 2 +- .../Microsoft.AspNetCore.Http.Features.csproj | 6 +- ...ft.AspNetCore.Http.Features.netcoreapp.cs} | 0 .../Microsoft.AspNetCore.Http.Features.csproj | 4 +- ...soft.AspNetCore.Http.Features.Tests.csproj | 2 +- ...crosoft.AspNetCore.Http.Performance.csproj | 2 +- .../Http/ref/Microsoft.AspNetCore.Http.csproj | 6 +- ...> Microsoft.AspNetCore.Http.netcoreapp.cs} | 0 .../Http/src/Microsoft.AspNetCore.Http.csproj | 2 +- .../Microsoft.AspNetCore.Http.Tests.csproj | 2 +- .../Owin/ref/Microsoft.AspNetCore.Owin.csproj | 6 +- ...> Microsoft.AspNetCore.Owin.netcoreapp.cs} | 0 .../Owin/src/Microsoft.AspNetCore.Owin.csproj | 2 +- .../Microsoft.AspNetCore.Owin.Tests.csproj | 2 +- ...oft.AspNetCore.Routing.Abstractions.csproj | 6 +- ...etCore.Routing.Abstractions.netcoreapp.cs} | 0 ...oft.AspNetCore.Routing.Abstractions.csproj | 2 +- ...Core.Mvc.Routing.Abstractions.Tests.csproj | 2 +- ...soft.AspNetCore.Routing.Performance.csproj | 2 +- .../ref/Microsoft.AspNetCore.Routing.csproj | 6 +- ...icrosoft.AspNetCore.Routing.netcoreapp.cs} | 0 .../src/Microsoft.AspNetCore.Routing.csproj | 2 +- ....AspNetCore.Routing.FunctionalTests.csproj | 2 +- .../Microsoft.AspNetCore.Routing.Tests.csproj | 2 +- .../testassets/Benchmarks/Benchmarks.csproj | 2 +- .../RoutingSandbox/RoutingSandbox.csproj | 2 +- .../RoutingWebSite/RoutingWebSite.csproj | 2 +- .../tools/Swaggatherer/Swaggatherer.csproj | 2 +- ...AspNetCore.WebUtilities.Performance.csproj | 2 +- .../Microsoft.AspNetCore.WebUtilities.csproj | 6 +- ...oft.AspNetCore.WebUtilities.netcoreapp.cs} | 0 .../Microsoft.AspNetCore.WebUtilities.csproj | 2 +- ...osoft.AspNetCore.WebUtilities.Tests.csproj | 2 +- ...oft.AspNetCore.Http.Microbenchmarks.csproj | 2 +- .../HttpAbstractions.SampleApp.csproj | 2 +- .../ApiAuthSample/ApiAuthSample.csproj | 2 +- ...ore.ApiAuthorization.IdentityServer.csproj | 2 +- ...iAuthorization.IdentityServer.Tests.csproj | 2 +- .../ref/Microsoft.AspNetCore.Identity.csproj | 6 +- ...crosoft.AspNetCore.Identity.netcoreapp.cs} | 0 .../src/Microsoft.AspNetCore.Identity.csproj | 2 +- ...etCore.Identity.EntityFrameworkCore.csproj | 6 +- ...dentity.EntityFrameworkCore.netcoreapp.cs} | 0 ...etCore.Identity.EntityFrameworkCore.csproj | 2 +- ...y.EntityFrameworkCore.InMemory.Test.csproj | 2 +- ...e.Identity.EntityFrameworkCore.Test.csproj | 2 +- .../Microsoft.Extensions.Identity.Core.csproj | 6 +- ...ft.Extensions.Identity.Core.netcoreapp.cs} | 0 .../Microsoft.Extensions.Identity.Core.csproj | 4 +- .../Extensions.Core/src/PasswordHasher.cs | 4 +- ...icrosoft.Extensions.Identity.Stores.csproj | 6 +- ....Extensions.Identity.Stores.netcoreapp.cs} | 0 ...icrosoft.Extensions.Identity.Stores.csproj | 4 +- ...etCore.Identity.Specification.Tests.csproj | 2 +- .../Microsoft.AspNetCore.Identity.UI.csproj | 6 +- ...soft.AspNetCore.Identity.UI.netcoreapp.cs} | 0 .../Microsoft.AspNetCore.Identity.UI.csproj | 2 +- .../IdentitySample.DefaultUI.csproj | 2 +- .../IdentitySample.Mvc.csproj | 2 +- ...AspNetCore.Identity.FunctionalTests.csproj | 8 +- .../Microsoft.AspNetCore.Identity.Test.csproj | 2 +- ...t.AspNetCore.Identity.InMemory.Test.csproj | 2 +- .../Identity.DefaultUI.WebSite.csproj | 2 +- .../CORS/ref/Microsoft.AspNetCore.Cors.csproj | 6 +- ...> Microsoft.AspNetCore.Cors.netcoreapp.cs} | 0 .../CORS/src/Microsoft.AspNetCore.Cors.csproj | 2 +- .../CORS.FunctionalTests.csproj | 2 +- .../CorsMiddlewareFunctionalTest.cs | 4 +- .../Microsoft.AspNetCore.Cors.Test.csproj | 2 +- .../CorsMiddlewareWebSite.csproj | 2 +- .../TestDestination/TestDestination.csproj | 2 +- .../testassets/TestOrigin/TestOrigin.csproj | 2 +- ....ConcurrencyLimiter.Microbenchmarks.csproj | 2 +- ...osoft.AspNetCore.ConcurrencyLimiter.csproj | 6 +- ...pNetCore.ConcurrencyLimiter.netcoreapp.cs} | 0 .../sample/ConcurrencyLimiterSample.csproj | 2 +- ...osoft.AspNetCore.ConcurrencyLimiter.csproj | 2 +- ...AspNetCore.ConcurrencyLimiter.Tests.csproj | 2 +- ...AspNetCore.Diagnostics.Abstractions.csproj | 6 +- ...re.Diagnostics.Abstractions.netcoreapp.cs} | 0 ...AspNetCore.Diagnostics.Abstractions.csproj | 2 +- ...ore.Diagnostics.EntityFrameworkCore.csproj | 6 +- ...nostics.EntityFrameworkCore.netcoreapp.cs} | 0 ...ore.Diagnostics.EntityFrameworkCore.csproj | 2 +- .../Diagnostics.EFCore.FunctionalTests.csproj | 2 +- ...agnostics.EntityFrameworkCore.Tests.csproj | 2 +- .../Microsoft.AspNetCore.Diagnostics.csproj | 6 +- ...soft.AspNetCore.Diagnostics.netcoreapp.cs} | 0 .../Microsoft.AspNetCore.Diagnostics.csproj | 2 +- .../Diagnostics.FunctionalTests.csproj | 2 +- ...rosoft.AspNetCore.Diagnostics.Tests.csproj | 2 +- .../ClassLibraryWithPortablePdbs.csproj | 2 +- .../DatabaseErrorPageSample.csproj | 2 +- .../DeveloperExceptionPageSample.csproj | 2 +- .../ExceptionHandlerSample.csproj | 2 +- .../StatusCodePagesSample.csproj | 2 +- .../WelcomePageSample.csproj | 2 +- ...rosoft.AspNetCore.HeaderPropagation.csproj | 6 +- ...spNetCore.HeaderPropagation.netcoreapp.cs} | 0 .../HeaderPropagationSample.csproj | 2 +- ...rosoft.AspNetCore.HeaderPropagation.csproj | 2 +- ....AspNetCore.HeaderPropagation.Tests.csproj | 2 +- ...lthChecks.EntityFrameworkCore.Tests.csproj | 2 +- ...AspNetCore.Diagnostics.HealthChecks.csproj | 6 +- ...re.Diagnostics.HealthChecks.netcoreapp.cs} | 0 ...AspNetCore.Diagnostics.HealthChecks.csproj | 4 +- ...Core.Diagnostics.HealthChecks.Tests.csproj | 2 +- .../HealthChecksSample.csproj | 2 +- .../Microsoft.AspNetCore.HostFiltering.csproj | 6 +- ...ft.AspNetCore.HostFiltering.netcoreapp.cs} | 0 .../sample/HostFilteringSample.csproj | 2 +- .../Microsoft.AspNetCore.HostFiltering.csproj | 2 +- ...soft.AspNetCore.HostFiltering.Tests.csproj | 2 +- .../Microsoft.AspNetCore.HttpOverrides.csproj | 6 +- ...ft.AspNetCore.HttpOverrides.netcoreapp.cs} | 0 .../sample/HttpOverridesSample.csproj | 2 +- .../Microsoft.AspNetCore.HttpOverrides.csproj | 2 +- ...soft.AspNetCore.HttpOverrides.Tests.csproj | 2 +- .../Microsoft.AspNetCore.HttpsPolicy.csproj | 6 +- ...soft.AspNetCore.HttpsPolicy.netcoreapp.cs} | 0 .../sample/HttpsPolicySample.csproj | 2 +- .../Microsoft.AspNetCore.HttpsPolicy.csproj | 2 +- ...rosoft.AspNetCore.HttpsPolicy.Tests.csproj | 2 +- ...oft.AspNetCore.Localization.Routing.csproj | 6 +- ...etCore.Localization.Routing.netcoreapp.cs} | 0 ...oft.AspNetCore.Localization.Routing.csproj | 2 +- ...pNetCore.Localization.Routing.Tests.csproj | 2 +- .../Microsoft.AspNetCore.Localization.csproj | 6 +- ...oft.AspNetCore.Localization.netcoreapp.cs} | 0 .../sample/LocalizationSample.csproj | 2 +- .../Microsoft.AspNetCore.Localization.csproj | 2 +- ...etCore.Localization.FunctionalTests.csproj | 2 +- ...osoft.AspNetCore.Localization.Tests.csproj | 2 +- .../LocalizationWebsite.csproj | 2 +- ...osoft.AspNetCore.MiddlewareAnalysis.csproj | 6 +- ...pNetCore.MiddlewareAnalysis.netcoreapp.cs} | 0 .../MiddlewareAnalysisSample.csproj | 2 +- ...osoft.AspNetCore.MiddlewareAnalysis.csproj | 2 +- ...AspNetCore.MiddlewareAnalysis.Tests.csproj | 2 +- .../Microsoft.AspNetCore.NodeServices.csproj | 6 +- ...oft.AspNetCore.NodeServices.netcoreapp.cs} | 0 .../NodeServicesExamples.csproj | 2 +- .../Microsoft.AspNetCore.NodeServices.csproj | 2 +- ...osoft.AspNetCore.NodeServices.Tests.csproj | 2 +- ...etCore.ResponseCaching.Abstractions.csproj | 6 +- ...esponseCaching.Abstractions.netcoreapp.cs} | 0 ...etCore.ResponseCaching.Abstractions.csproj | 2 +- ...icrosoft.AspNetCore.ResponseCaching.csproj | 6 +- ....AspNetCore.ResponseCaching.netcoreapp.cs} | 0 .../ResponseCachingSample.csproj | 2 +- ...icrosoft.AspNetCore.ResponseCaching.csproj | 2 +- ...ft.AspNetCore.ResponseCaching.Tests.csproj | 2 +- ...ore.ResponseCompression.Performance.csproj | 2 +- ...soft.AspNetCore.ResponseCompression.csproj | 6 +- ...NetCore.ResponseCompression.netcoreapp.cs} | 0 .../sample/ResponseCompressionSample.csproj | 2 +- ...soft.AspNetCore.ResponseCompression.csproj | 2 +- ...spNetCore.ResponseCompression.Tests.csproj | 2 +- .../ref/Microsoft.AspNetCore.Rewrite.csproj | 6 +- ...icrosoft.AspNetCore.Rewrite.netcoreapp.cs} | 0 .../Rewrite/sample/RewriteSample.csproj | 2 +- .../src/Microsoft.AspNetCore.Rewrite.csproj | 2 +- .../Microsoft.AspNetCore.Rewrite.Tests.csproj | 2 +- .../ref/Microsoft.AspNetCore.Session.csproj | 6 +- ...icrosoft.AspNetCore.Session.netcoreapp.cs} | 0 .../Session/samples/SessionSample.csproj | 2 +- .../src/Microsoft.AspNetCore.Session.csproj | 2 +- .../Microsoft.AspNetCore.Session.Tests.csproj | 2 +- ...t.AspNetCore.SpaServices.Extensions.csproj | 6 +- ...Core.SpaServices.Extensions.netcoreapp.cs} | 0 ...t.AspNetCore.SpaServices.Extensions.csproj | 2 +- ...etCore.SpaServices.Extensions.Tests.csproj | 2 +- .../Microsoft.AspNetCore.SpaServices.csproj | 6 +- ...soft.AspNetCore.SpaServices.netcoreapp.cs} | 0 .../Microsoft.AspNetCore.SpaServices.csproj | 2 +- .../Microsoft.AspNetCore.StaticFiles.csproj | 6 +- ...soft.AspNetCore.StaticFiles.netcoreapp.cs} | 0 .../StaticFileSample/StaticFileSample.csproj | 2 +- .../Microsoft.AspNetCore.StaticFiles.csproj | 2 +- ...NetCore.StaticFiles.FunctionalTests.csproj | 2 +- ...rosoft.AspNetCore.StaticFiles.Tests.csproj | 2 +- .../Microsoft.AspNetCore.WebSockets.csproj | 6 +- ...osoft.AspNetCore.WebSockets.netcoreapp.cs} | 0 .../WebSockets/samples/EchoApp/EchoApp.csproj | 2 +- .../Microsoft.AspNetCore.WebSockets.csproj | 2 +- .../Autobahn/AutobahnTester.cs | 2 +- .../AutobahnTestApp/AutobahnTestApp.csproj | 2 +- ...NetCore.WebSockets.ConformanceTests.csproj | 2 +- ...crosoft.AspNetCore.WebSockets.Tests.csproj | 2 +- .../samples/MusicStore/MusicStore.csproj | 2 +- .../MusicStore.E2ETests/DotnetRunTests.cs | 2 +- .../MusicStore.E2ETests.csproj | 2 +- .../NtlmAuthentationTest.cs | 2 +- .../MusicStore.E2ETests/OpenIdConnectTests.cs | 2 +- .../MusicStore.E2ETests/PublishAndRunTests.cs | 2 +- .../test/MusicStore.E2ETests/SmokeTests.cs | 2 +- .../SmokeTestsOnNanoServer.cs | 2 +- .../MusicStore.E2ETests/StoreSmokeTests.cs | 2 +- .../MusicStore.Test/MusicStore.Test.csproj | 2 +- ...crosoft.AspNetCore.Mvc.Abstractions.csproj | 6 +- ...AspNetCore.Mvc.Abstractions.netcoreapp.cs} | 0 ...crosoft.AspNetCore.Mvc.Abstractions.csproj | 2 +- ...ft.AspNetCore.Mvc.Abstractions.Test.csproj | 2 +- .../test/Mvc.Analyzers.Test.csproj | 2 +- .../test/Mvc.Api.Analyzers.Test.csproj | 2 +- ...icrosoft.AspNetCore.Mvc.ApiExplorer.csproj | 6 +- ....AspNetCore.Mvc.ApiExplorer.netcoreapp.cs} | 0 ...icrosoft.AspNetCore.Mvc.ApiExplorer.csproj | 2 +- ...oft.AspNetCore.Mvc.ApiExplorer.Test.csproj | 2 +- .../ref/Microsoft.AspNetCore.Mvc.Core.csproj | 6 +- ...crosoft.AspNetCore.Mvc.Core.netcoreapp.cs} | 0 .../src/Microsoft.AspNetCore.Mvc.Core.csproj | 2 +- .../Microsoft.AspNetCore.Mvc.Core.Test.csproj | 2 +- .../ref/Microsoft.AspNetCore.Mvc.Cors.csproj | 6 +- ...crosoft.AspNetCore.Mvc.Cors.netcoreapp.cs} | 0 .../src/Microsoft.AspNetCore.Mvc.Cors.csproj | 2 +- .../Microsoft.AspNetCore.Mvc.Cors.Test.csproj | 2 +- ...soft.AspNetCore.Mvc.DataAnnotations.csproj | 6 +- ...NetCore.Mvc.DataAnnotations.netcoreapp.cs} | 0 ...soft.AspNetCore.Mvc.DataAnnotations.csproj | 2 +- ...AspNetCore.Mvc.DataAnnotations.Test.csproj | 2 +- ...soft.AspNetCore.Mvc.Formatters.Json.csproj | 6 +- ...NetCore.Mvc.Formatters.Json.netcoreapp.cs} | 0 ...soft.AspNetCore.Mvc.Formatters.Json.csproj | 2 +- ...osoft.AspNetCore.Mvc.Formatters.Xml.csproj | 6 +- ...pNetCore.Mvc.Formatters.Xml.netcoreapp.cs} | 0 ...osoft.AspNetCore.Mvc.Formatters.Xml.csproj | 2 +- ....AspNetCore.Mvc.Formatters.Xml.Test.csproj | 2 +- ...crosoft.AspNetCore.Mvc.Localization.csproj | 6 +- ...AspNetCore.Mvc.Localization.netcoreapp.cs} | 0 ...crosoft.AspNetCore.Mvc.Localization.csproj | 2 +- ...ft.AspNetCore.Mvc.Localization.Test.csproj | 2 +- ...osoft.AspNetCore.Mvc.NewtonsoftJson.csproj | 6 +- ...pNetCore.Mvc.NewtonsoftJson.netcoreapp.cs} | 0 ...osoft.AspNetCore.Mvc.NewtonsoftJson.csproj | 2 +- ....AspNetCore.Mvc.NewtonsoftJson.Test.csproj | 2 +- ...etCore.Mvc.Razor.RuntimeCompilation.csproj | 6 +- ...vc.Razor.RuntimeCompilation.netcoreapp.cs} | 0 ...etCore.Mvc.Razor.RuntimeCompilation.csproj | 4 +- ...tCore.Mvc.Razor.RuntimeCompilation.targets | 0 ...e.Mvc.Razor.RuntimeCompilation.Test.csproj | 2 +- .../ref/Microsoft.AspNetCore.Mvc.Razor.csproj | 6 +- ...rosoft.AspNetCore.Mvc.Razor.netcoreapp.cs} | 0 .../src/Microsoft.AspNetCore.Mvc.Razor.csproj | 2 +- ...Microsoft.AspNetCore.Mvc.Razor.Test.csproj | 2 +- .../Mvc.RazorPages/ref/Directory.Build.props | 2 +- ...Microsoft.AspNetCore.Mvc.RazorPages.csproj | 6 +- ...t.AspNetCore.Mvc.RazorPages.netcoreapp.cs} | 0 ...re.Mvc.RazorPages.netcoreapp5.0.Manual.cs} | 0 ...Microsoft.AspNetCore.Mvc.RazorPages.csproj | 2 +- ...soft.AspNetCore.Mvc.RazorPages.Test.csproj | 2 +- ...Microsoft.AspNetCore.Mvc.TagHelpers.csproj | 6 +- ...t.AspNetCore.Mvc.TagHelpers.netcoreapp.cs} | 0 ...Microsoft.AspNetCore.Mvc.TagHelpers.csproj | 2 +- ...soft.AspNetCore.Mvc.TagHelpers.Test.csproj | 2 +- .../Microsoft.AspNetCore.Mvc.Testing.csproj | 6 +- ...soft.AspNetCore.Mvc.Testing.netcoreapp.cs} | 0 .../Microsoft.AspNetCore.Mvc.Testing.csproj | 2 +- ...crosoft.AspNetCore.Mvc.ViewFeatures.csproj | 6 +- ...AspNetCore.Mvc.ViewFeatures.netcoreapp.cs} | 0 ...crosoft.AspNetCore.Mvc.ViewFeatures.csproj | 2 +- ...ft.AspNetCore.Mvc.ViewFeatures.Test.csproj | 2 +- .../Mvc/ref/Microsoft.AspNetCore.Mvc.csproj | 6 +- ...=> Microsoft.AspNetCore.Mvc.netcoreapp.cs} | 0 .../Mvc/src/Microsoft.AspNetCore.Mvc.csproj | 2 +- .../test/Microsoft.AspNetCore.Mvc.Test.csproj | 2 +- .../benchmarkapps/BasicApi/BasicApi.csproj | 2 +- .../BasicViews/BasicViews.csproj | 2 +- .../RazorRendering/RazorRendering.csproj | 2 +- ...ft.AspNetCore.Mvc.Performance.Views.csproj | 2 +- ...icrosoft.AspNetCore.Mvc.Performance.csproj | 2 +- src/Mvc/samples/MvcSandbox/MvcSandbox.csproj | 2 +- ...soft.AspNetCore.Mvc.Core.TestCommon.csproj | 2 +- ...pNetCore.Mvc.TestDiagnosticListener.csproj | 2 +- ...oft.AspNetCore.Mvc.Views.TestCommon.csproj | 2 +- ...soft.AspNetCore.Mvc.FunctionalTests.csproj | 2 +- ...oft.AspNetCore.Mvc.IntegrationTests.csproj | 2 +- .../ApiExplorerWebSite.csproj | 2 +- .../ApplicationModelWebSite.csproj | 2 +- .../WebSites/BasicWebSite/BasicWebSite.csproj | 2 +- ...ControllersFromServicesClassLibrary.csproj | 2 +- .../ControllersFromServicesWebSite.csproj | 2 +- .../WebSites/CorsWebSite/CorsWebSite.csproj | 2 +- .../ErrorPageMiddlewareWebSite.csproj | 2 +- .../WebSites/FilesWebSite/FilesWebSite.csproj | 2 +- .../FormatterWebSite/FormatterWebSite.csproj | 2 +- .../GenericHostWebSite.csproj | 2 +- .../HtmlGenerationWebSite.csproj | 2 +- .../RazorBuildWebSite.PrecompiledViews.csproj | 2 +- .../RazorBuildWebSite.Views.csproj | 2 +- .../RazorBuildWebSite.csproj | 2 +- .../RazorPagesClassLibrary.csproj | 2 +- .../RazorPagesWebSite.csproj | 2 +- .../WebSites/RazorWebSite/RazorWebSite.csproj | 2 +- .../RoutingWebSite/Mvc.RoutingWebSite.csproj | 2 +- .../SecurityWebSite/SecurityWebSite.csproj | 2 +- .../SimpleWebSite/SimpleWebSite.csproj | 2 +- .../TagHelpersWebSite.csproj | 2 +- .../VersioningWebSite.csproj | 2 +- .../XmlFormattersWebSite.csproj | 2 +- ...oft.DotNet.Web.Client.ItemTemplates.csproj | 2 +- .../Microsoft.DotNet.Web.ItemTemplates.csproj | 2 +- .../BlazorServerWeb-CSharp.csproj.in | 2 +- .../EmptyWeb-CSharp.csproj.in | 2 +- .../EmptyWeb-FSharp.fsproj.in | 2 +- .../GrpcService-CSharp.csproj.in | 2 +- ...crosoft.DotNet.Web.ProjectTemplates.csproj | 2 +- .../RazorClassLibrary-CSharp.csproj.in | 2 +- .../RazorPagesWeb-CSharp.csproj.in | 2 +- .../StarterWeb-CSharp.csproj.in | 2 +- .../StarterWeb-FSharp.fsproj.in | 2 +- .../WebApi-CSharp.csproj.in | 2 +- .../WebApi-FSharp.fsproj.in | 2 +- .../Worker-CSharp.csproj.in | 2 +- .../.template.config/template.json | 8 +- .../.template.config/template.json | 8 +- .../.template.config/template.json | 8 +- .../.template.config/template.json | 6 +- .../.template.config/template.json | 6 +- .../.template.config/template.json | 8 +- .../.template.config/template.json | 8 +- .../.template.config/template.json | 8 +- .../.template.config/template.json | 8 +- .../.template.config/template.json | 8 +- .../.template.config/template.json | 8 +- .../Angular-CSharp.csproj.in | 2 +- ...oft.DotNet.Web.Spa.ProjectTemplates.csproj | 2 +- .../React-CSharp.csproj.in | 2 +- .../ReactRedux-CSharp.csproj.in | 2 +- .../.template.config/template.json | 8 +- .../.template.config/template.json | 8 +- .../.template.config/template.json | 8 +- .../scripts/Test-Template.ps1 | 4 +- .../test/BlazorServerTemplateTest.cs | 4 +- .../test/EmptyWebTemplateTest.cs | 2 +- src/ProjectTemplates/test/Helpers/Project.cs | 2 +- .../test/Helpers/TemplatePackageInstaller.cs | 4 +- .../test/IdentityUIPackageTest.cs | 2 +- .../Infrastructure/Directory.Build.props.in | 3 + .../Infrastructure/TemplateTests.props.in | 2 - src/ProjectTemplates/test/MvcTemplateTest.cs | 4 +- .../test/ProjectTemplates.Tests.csproj | 10 +- .../test/RazorClassLibraryTemplateTest.cs | 4 +- .../test/RazorPagesTemplateTest.cs | 4 +- .../SpaTemplateTest/SpaTemplateTestBase.cs | 2 +- .../test/WebApiTemplateTest.cs | 2 +- .../test/WorkerTemplateTest.cs | 2 +- .../DotNetToolsInstaller.csproj | 2 +- .../Microsoft.AspNetCore.Razor.Runtime.csproj | 6 +- ...ft.AspNetCore.Razor.Runtime.netcoreapp.cs} | 0 .../Microsoft.AspNetCore.Razor.Runtime.csproj | 2 +- ...osoft.AspNetCore.Razor.Runtime.Test.csproj | 2 +- .../ref/Microsoft.AspNetCore.Razor.csproj | 6 +- ... Microsoft.AspNetCore.Razor.netcoreapp.cs} | 0 .../src/Microsoft.AspNetCore.Razor.csproj | 2 +- .../Microsoft.AspNetCore.Razor.Test.csproj | 2 +- ...pNetCore.Authentication.Certificate.csproj | 6 +- ....Authentication.Certificate.netcoreapp.cs} | 0 .../Certificate.Sample.csproj | 2 +- ...pNetCore.Authentication.Certificate.csproj | 2 +- ...t.AspNetCore.Authentication.Cookies.csproj | 6 +- ...Core.Authentication.Cookies.netcoreapp.cs} | 0 .../samples/CookieSample/CookieSample.csproj | 2 +- .../CookieSessionSample.csproj | 2 +- ...t.AspNetCore.Authentication.Cookies.csproj | 2 +- ...Microsoft.AspNetCore.Authentication.csproj | 6 +- ...t.AspNetCore.Authentication.netcoreapp.cs} | 0 ...Microsoft.AspNetCore.Authentication.csproj | 2 +- ....AspNetCore.Authentication.Facebook.csproj | 6 +- ...ore.Authentication.Facebook.netcoreapp.cs} | 0 ....AspNetCore.Authentication.Facebook.csproj | 2 +- ...ft.AspNetCore.Authentication.Google.csproj | 6 +- ...tCore.Authentication.Google.netcoreapp.cs} | 0 ...ft.AspNetCore.Authentication.Google.csproj | 2 +- ...AspNetCore.Authentication.JwtBearer.csproj | 6 +- ...re.Authentication.JwtBearer.netcoreapp.cs} | 0 .../JwtBearerSample/JwtBearerSample.csproj | 2 +- ...AspNetCore.Authentication.JwtBearer.csproj | 2 +- ...ore.Authentication.MicrosoftAccount.csproj | 6 +- ...entication.MicrosoftAccount.netcoreapp.cs} | 0 ...ore.Authentication.MicrosoftAccount.csproj | 2 +- ...AspNetCore.Authentication.Negotiate.csproj | 6 +- ...re.Authentication.Negotiate.netcoreapp.cs} | 0 .../NegotiateAuthSample.csproj | 2 +- ...AspNetCore.Authentication.Negotiate.csproj | 2 +- ...entication.Negotiate.FunctionalTest.csproj | 2 +- ...tCore.Authentication.Negotiate.Test.csproj | 2 +- .../Negotiate.Client/Negotiate.Client.csproj | 2 +- .../Negotiate.Server/Negotiate.Server.csproj | 2 +- ...oft.AspNetCore.Authentication.OAuth.csproj | 6 +- ...etCore.Authentication.OAuth.netcoreapp.cs} | 0 ...oft.AspNetCore.Authentication.OAuth.csproj | 2 +- ...etCore.Authentication.OpenIdConnect.csproj | 6 +- ...uthentication.OpenIdConnect.netcoreapp.cs} | 0 .../OpenIdConnect.AzureAdSample.csproj | 2 +- .../OpenIdConnectSample.csproj | 2 +- ...etCore.Authentication.OpenIdConnect.csproj | 2 +- ...t.AspNetCore.Authentication.Twitter.csproj | 6 +- ...Core.Authentication.Twitter.netcoreapp.cs} | 0 ...t.AspNetCore.Authentication.Twitter.csproj | 2 +- ...NetCore.Authentication.WsFederation.csproj | 6 +- ...Authentication.WsFederation.netcoreapp.cs} | 0 .../samples/WsFedSample/WsFedSample.csproj | 2 +- ...NetCore.Authentication.WsFederation.csproj | 2 +- .../samples/SocialSample/SocialSample.csproj | 2 +- ...soft.AspNetCore.Authentication.Test.csproj | 2 +- .../Microsoft.AspNetCore.Authorization.csproj | 6 +- ...ft.AspNetCore.Authorization.netcoreapp.cs} | 0 .../Microsoft.AspNetCore.Authorization.csproj | 4 +- ...oft.AspNetCore.Authorization.Policy.csproj | 6 +- ...etCore.Authorization.Policy.netcoreapp.cs} | 0 ...oft.AspNetCore.Authorization.Policy.csproj | 2 +- ...osoft.AspNetCore.Authorization.Test.csproj | 2 +- .../Microsoft.AspNetCore.CookiePolicy.csproj | 6 +- ...oft.AspNetCore.CookiePolicy.netcoreapp.cs} | 0 .../CookiePolicySample.csproj | 2 +- .../Microsoft.AspNetCore.CookiePolicy.csproj | 2 +- ...rosoft.AspNetCore.CookiePolicy.Test.csproj | 2 +- ...oft.AspNetCore.Security.Performance.csproj | 2 +- .../ClaimsTransformation.csproj | 2 +- src/Security/samples/Cookies/Cookies.csproj | 2 +- .../CustomPolicyProvider.csproj | 2 +- .../DynamicSchemes/DynamicSchemes.csproj | 2 +- .../Identity.ExternalClaims.csproj | 2 +- .../PathSchemeSelection.csproj | 2 +- .../StaticFilesAuth/StaticFilesAuth.csproj | 2 +- .../AuthSamples.FunctionalTests.csproj | 16 +- ...AspNetCore.Connections.Abstractions.csproj | 6 +- ...re.Connections.Abstractions.netcoreapp.cs} | 0 ...AspNetCore.Connections.Abstractions.csproj | 4 +- ...Microsoft.AspNetCore.Server.HttpSys.csproj | 6 +- ...t.AspNetCore.Server.HttpSys.netcoreapp.cs} | 0 .../samples/HotAddSample/HotAddSample.csproj | 2 +- .../SelfHostServer/SelfHostServer.csproj | 2 +- ...Microsoft.AspNetCore.Server.HttpSys.csproj | 2 +- ...Core.Server.HttpSys.FunctionalTests.csproj | 2 +- ...oft.AspNetCore.Server.HttpSys.Tests.csproj | 2 +- .../Microsoft.AspNetCore.ANCMSymbols.csproj | 2 +- .../IIS.Performance/IIS.Performance.csproj | 2 +- .../Microsoft.AspNetCore.Server.IIS.csproj | 6 +- ...osoft.AspNetCore.Server.IIS.netcoreapp.cs} | 0 .../NativeIISSample/NativeIISSample.csproj | 2 +- .../Microsoft.AspNetCore.Server.IIS.csproj | 4 +- .../Common.FunctionalTests/BasicAuthTests.cs | 2 +- .../ClientCertificateTests.cs | 2 +- .../CommonStartupTests.cs | 2 +- .../test/Common.FunctionalTests/HttpsTests.cs | 2 +- .../Inprocess/StartupTests.cs | 2 +- .../Common.FunctionalTests/LogFileTests.cs | 2 +- .../OutOfProcess/AspNetCorePortTests.cs | 2 +- .../OutOfProcess/HelloWorldTest.cs | 2 +- .../PublishedSitesFixture.cs | 2 +- .../Utilities/IISTestSiteFixture.cs | 2 +- .../WindowsAuthTests.cs | 2 +- .../IIS.FunctionalTests.csproj | 2 +- .../IIS.NewHandler.FunctionalTests.csproj | 2 +- .../IIS.NewShim.FunctionalTests.csproj | 2 +- .../IIS/IIS/test/IIS.Tests/IIS.Tests.csproj | 2 +- .../IISExpress.FunctionalTests.csproj | 2 +- .../OutOfProcess/NtlmAuthentationTest.cs | 2 +- .../IIS.Common.TestLib.csproj | 2 +- .../InProcessNewShimWebSite.csproj | 4 +- .../InProcessWebSite/InProcessWebSite.csproj | 4 +- .../testassets/TestTasks/TestTasks.csproj | 2 +- ...ft.AspNetCore.Server.IISIntegration.csproj | 6 +- ...tCore.Server.IISIntegration.netcoreapp.cs} | 0 .../samples/IISSample/IISSample.csproj | 2 +- ...ft.AspNetCore.Server.IISIntegration.csproj | 2 +- ...NetCore.Server.IISIntegration.Tests.csproj | 2 +- .../src/IISExpressDeployer.cs | 2 +- ...tCore.Server.IntegrationTesting.IIS.csproj | 2 +- ...soft.AspNetCore.Server.Kestrel.Core.csproj | 6 +- ...NetCore.Server.Kestrel.Core.netcoreapp.cs} | 0 ...soft.AspNetCore.Server.Kestrel.Core.csproj | 2 +- ...spNetCore.Server.Kestrel.Core.Tests.csproj | 2 +- ...Microsoft.AspNetCore.Server.Kestrel.csproj | 6 +- ...t.AspNetCore.Server.Kestrel.netcoreapp.cs} | 0 ...Microsoft.AspNetCore.Server.Kestrel.csproj | 2 +- ...oft.AspNetCore.Server.Kestrel.Tests.csproj | 2 +- ...Core.Server.Kestrel.Transport.Libuv.csproj | 6 +- ...ver.Kestrel.Transport.Libuv.netcoreapp.cs} | 0 ...Core.Server.Kestrel.Transport.Libuv.csproj | 2 +- ...erver.Kestrel.Transport.Libuv.Tests.csproj | 2 +- ...re.Server.Kestrel.Transport.Sockets.csproj | 6 +- ...r.Kestrel.Transport.Sockets.netcoreapp.cs} | 0 ...re.Server.Kestrel.Transport.Sockets.csproj | 2 +- ...pNetCore.Server.Kestrel.Performance.csproj | 2 +- .../BenchmarkApplication.HttpConnection.cs | 4 +- .../PlatformBenchmarks.csproj | 2 +- .../Http2SampleApp/Http2SampleApp.csproj | 2 +- .../LargeResponseApp/LargeResponseApp.csproj | 2 +- .../samples/PlaintextApp/PlaintextApp.csproj | 2 +- .../SampleApp/Kestrel.SampleApp.csproj | 2 +- .../SystemdTestApp/SystemdTestApp.csproj | 2 +- src/Servers/Kestrel/stress/HttpStress.csproj | 2 +- .../InMemory.FunctionalTests.csproj | 2 +- .../Interop.FunctionalTests.csproj | 2 +- .../Libuv.BindTests/Libuv.BindTests.csproj | 2 +- .../Libuv.FunctionalTests.csproj | 2 +- .../Sockets.BindTests.csproj | 2 +- .../Sockets.FunctionalTests.csproj | 2 +- .../tools/CodeGenerator/CodeGenerator.csproj | 2 +- .../test/FunctionalTests/HelloWorldTest.cs | 2 +- .../FunctionalTests/NtlmAuthenticationTest.cs | 2 +- .../ResponseCompressionTests.cs | 8 +- .../test/FunctionalTests/ResponseTests.cs | 4 +- .../ServerComparison.FunctionalTests.csproj | 2 +- .../ServerComparison.TestSites.csproj | 2 +- src/Shared/ErrorPage/GeneratePage.ps1 | 2 +- src/Shared/WebEncoders/WebEncoders.cs | 8 +- .../Microsoft.AspNetCore.Shared.Tests.csproj | 2 +- ...NetCore.SignalR.Client.Core.netcoreapp.cs} | 0 ...Core.SignalR.Client.FunctionalTests.csproj | 2 +- ...oft.AspNetCore.SignalR.Client.Tests.csproj | 2 +- ...ore.Http.Connections.Client.netcoreapp.cs} | 0 .../SignalR.Client.FunctionalTestApp.csproj | 2 +- .../ts/FunctionalTests/scripts/run-tests.ts | 2 +- ....AspNetCore.Http.Connections.Common.csproj | 6 +- ...ore.Http.Connections.Common.netcoreapp.cs} | 0 ....AspNetCore.Http.Connections.Common.csproj | 4 +- ...crosoft.AspNetCore.Http.Connections.csproj | 6 +- ...AspNetCore.Http.Connections.netcoreapp.cs} | 0 .../Transports/WebSocketsServerTransport.cs | 2 +- ...crosoft.AspNetCore.Http.Connections.csproj | 2 +- ...t.AspNetCore.Http.Connections.Tests.csproj | 2 +- ...t.AspNetCore.SignalR.Protocols.Json.csproj | 6 +- ...Core.SignalR.Protocols.Json.netcoreapp.cs} | 0 ...t.AspNetCore.SignalR.Protocols.Json.csproj | 4 +- .../common/Shared/AsyncEnumerableAdapters.cs | 2 +- .../common/Shared/MemoryBufferWriter.cs | 2 +- src/SignalR/common/Shared/PipeWriterStream.cs | 2 +- src/SignalR/common/Shared/StreamExtensions.cs | 4 +- .../common/Shared/Utf8BufferTextReader.cs | 2 +- .../common/Shared/Utf8BufferTextWriter.cs | 4 +- .../common/Shared/WebSocketExtensions.cs | 6 +- ...Microsoft.AspNetCore.SignalR.Common.csproj | 6 +- ...t.AspNetCore.SignalR.Common.netcoreapp.cs} | 0 ...Microsoft.AspNetCore.SignalR.Common.csproj | 4 +- .../Protocol/MemoryBufferWriterTests.cs | 2 +- ...oft.AspNetCore.SignalR.Common.Tests.csproj | 2 +- ...soft.AspNetCore.SignalR.Tests.Utils.csproj | 2 +- ....AspNetCore.SignalR.Microbenchmarks.csproj | 2 +- .../benchmarkapps/Crankier/Crankier.csproj | 2 +- src/SignalR/publish-apps.ps1 | 2 +- .../samples/ClientSample/ClientSample.csproj | 2 +- .../ClientSample/Tcp/SocketReceiver.cs | 2 +- .../samples/ClientSample/Tcp/SocketSender.cs | 4 +- .../JwtClientSample/JwtClientSample.csproj | 2 +- .../samples/JwtSample/JwtSample.csproj | 2 +- .../SignalRSamples/SignalRSamples.csproj | 2 +- .../SocialWeather/SocialWeather.csproj | 2 +- .../WebSocketSample/WebSocketSample.csproj | 2 +- .../Microsoft.AspNetCore.SignalR.Core.csproj | 6 +- ...oft.AspNetCore.SignalR.Core.netcoreapp.cs} | 0 .../Microsoft.AspNetCore.SignalR.Core.csproj | 2 +- .../ref/Microsoft.AspNetCore.SignalR.csproj | 6 +- ...icrosoft.AspNetCore.SignalR.netcoreapp.cs} | 0 .../src/Microsoft.AspNetCore.SignalR.csproj | 2 +- .../Microsoft.AspNetCore.SignalR.Tests.csproj | 2 +- ...NetCore.SignalR.Specification.Tests.csproj | 2 +- ...pNetCore.SignalR.StackExchangeRedis.csproj | 6 +- ....SignalR.StackExchangeRedis.netcoreapp.cs} | 0 ...pNetCore.SignalR.StackExchangeRedis.csproj | 2 +- ...re.SignalR.StackExchangeRedis.Tests.csproj | 2 +- src/SiteExtensions/LoggingBranch/LB.csproj | 2 +- .../Sdk/HostingStartup/HostingStartup.csproj | 2 +- src/SiteExtensions/Sdk/SiteExtension.targets | 6 +- ...NetCore.DeveloperCertificates.XPlat.csproj | 2 +- ...e.DeveloperCertificates.XPlat.Tests.csproj | 2 +- .../src/dotnet-dev-certs.csproj | 2 +- .../src/dotnet-sql-cache.csproj | 2 +- .../src/dotnet-user-secrets.csproj | 2 +- .../test/UserSecretsTestFixture.cs | 2 +- .../test/dotnet-user-secrets.Tests.csproj | 2 +- .../dotnet-watch/src/dotnet-watch.csproj | 2 +- src/Tools/dotnet-watch/test/ProgramTests.cs | 2 +- .../AppWithDeps/AppWithDeps.csproj | 2 +- .../GlobbingApp/GlobbingApp.csproj | 2 +- .../KitchenSink/KitchenSink.csproj | 2 +- .../TestProjects/NoDepsApp/NoDepsApp.csproj | 2 +- .../test/dotnet-watch.Tests.csproj | 3 +- 766 files changed, 1280 insertions(+), 1313 deletions(-) rename src/Antiforgery/ref/{Microsoft.AspNetCore.Antiforgery.netcoreapp3.0.cs => Microsoft.AspNetCore.Antiforgery.netcoreapp.cs} (100%) rename src/Azure/AzureAD/Authentication.AzureAD.UI/ref/{Microsoft.AspNetCore.Authentication.AzureAD.UI.netcoreapp3.0.cs => Microsoft.AspNetCore.Authentication.AzureAD.UI.netcoreapp.cs} (100%) rename src/Azure/AzureAD/Authentication.AzureADB2C.UI/ref/{Microsoft.AspNetCore.Authentication.AzureADB2C.UI.netcoreapp3.0.cs => Microsoft.AspNetCore.Authentication.AzureADB2C.UI.netcoreapp.cs} (100%) rename src/Azure/AzureAppServices.HostingStartup/ref/{Microsoft.AspNetCore.AzureAppServices.HostingStartup.netcoreapp3.0.cs => Microsoft.AspNetCore.AzureAppServices.HostingStartup.netcoreapp.cs} (100%) rename src/Azure/AzureAppServicesIntegration/ref/{Microsoft.AspNetCore.AzureAppServicesIntegration.netcoreapp3.0.cs => Microsoft.AspNetCore.AzureAppServicesIntegration.netcoreapp.cs} (100%) rename src/Components/Authorization/ref/{Microsoft.AspNetCore.Components.Authorization.netcoreapp3.0.cs => Microsoft.AspNetCore.Components.Authorization.netcoreapp.cs} (99%) rename src/Components/Blazor/Server/ref/{Microsoft.AspNetCore.Blazor.Server.netcoreapp3.0.cs => Microsoft.AspNetCore.Blazor.Server.netcoreapp.cs} (100%) rename src/Components/Components/ref/{Microsoft.AspNetCore.Components.netcoreapp3.0.cs => Microsoft.AspNetCore.Components.netcoreapp.cs} (100%) rename src/Components/Components/src/{Microsoft.AspNetCore.Components.netcoreapp3.0.nuspec => Microsoft.AspNetCore.Components.netcoreapp5.0.nuspec} (94%) rename src/Components/Forms/ref/{Microsoft.AspNetCore.Components.Forms.netcoreapp3.0.cs => Microsoft.AspNetCore.Components.Forms.netcoreapp.cs} (100%) rename src/Components/Server/ref/{Microsoft.AspNetCore.Components.Server.netcoreapp3.0.cs => Microsoft.AspNetCore.Components.Server.netcoreapp.cs} (100%) rename src/Components/Web/ref/{Microsoft.AspNetCore.Components.Web.netcoreapp3.0.cs => Microsoft.AspNetCore.Components.Web.netcoreapp.cs} (100%) rename src/DataProtection/Cryptography.KeyDerivation/ref/{Microsoft.AspNetCore.Cryptography.KeyDerivation.netcoreapp2.0.cs => Microsoft.AspNetCore.Cryptography.KeyDerivation.netcoreapp.cs} (100%) rename src/DataProtection/DataProtection/ref/{Microsoft.AspNetCore.DataProtection.netcoreapp3.0.cs => Microsoft.AspNetCore.DataProtection.netcoreapp.cs} (100%) rename src/DataProtection/Extensions/ref/{Microsoft.AspNetCore.DataProtection.Extensions.netcoreapp3.0.cs => Microsoft.AspNetCore.DataProtection.Extensions.netcoreapp.cs} (100%) rename src/DefaultBuilder/ref/{Microsoft.AspNetCore.netcoreapp3.0.cs => Microsoft.AspNetCore.netcoreapp.cs} (100%) rename src/Hosting/Abstractions/ref/{Microsoft.AspNetCore.Hosting.Abstractions.netcoreapp3.0.cs => Microsoft.AspNetCore.Hosting.Abstractions.netcoreapp.cs} (100%) rename src/Hosting/Hosting/ref/{Microsoft.AspNetCore.Hosting.netcoreapp3.0.cs => Microsoft.AspNetCore.Hosting.netcoreapp.cs} (100%) rename src/Hosting/Server.Abstractions/ref/{Microsoft.AspNetCore.Hosting.Server.Abstractions.netcoreapp3.0.cs => Microsoft.AspNetCore.Hosting.Server.Abstractions.netcoreapp.cs} (100%) rename src/Hosting/Server.IntegrationTesting/ref/{Microsoft.AspNetCore.Server.IntegrationTesting.netcoreapp3.0.cs => Microsoft.AspNetCore.Server.IntegrationTesting.netcoreapp.cs} (99%) rename src/Hosting/TestHost/ref/{Microsoft.AspNetCore.TestHost.netcoreapp3.0.cs => Microsoft.AspNetCore.TestHost.netcoreapp.cs} (100%) rename src/Hosting/WindowsServices/ref/{Microsoft.AspNetCore.Hosting.WindowsServices.netcoreapp3.0.cs => Microsoft.AspNetCore.Hosting.WindowsServices.netcoreapp.cs} (100%) rename src/Html/Abstractions/ref/{Microsoft.AspNetCore.Html.Abstractions.netcoreapp3.0.cs => Microsoft.AspNetCore.Html.Abstractions.netcoreapp.cs} (100%) rename src/Http/Authentication.Abstractions/ref/{Microsoft.AspNetCore.Authentication.Abstractions.netcoreapp3.0.cs => Microsoft.AspNetCore.Authentication.Abstractions.netcoreapp.cs} (100%) rename src/Http/Authentication.Core/ref/{Microsoft.AspNetCore.Authentication.Core.netcoreapp3.0.cs => Microsoft.AspNetCore.Authentication.Core.netcoreapp.cs} (100%) rename src/Http/Headers/ref/{Microsoft.Net.Http.Headers.netcoreapp3.0.cs => Microsoft.Net.Http.Headers.netcoreapp.cs} (100%) rename src/Http/Http.Abstractions/ref/{Microsoft.AspNetCore.Http.Abstractions.netcoreapp3.0.cs => Microsoft.AspNetCore.Http.Abstractions.netcoreapp.cs} (100%) rename src/Http/Http.Extensions/ref/{Microsoft.AspNetCore.Http.Extensions.netcoreapp3.0.cs => Microsoft.AspNetCore.Http.Extensions.netcoreapp.cs} (100%) rename src/Http/Http.Features/ref/{Microsoft.AspNetCore.Http.Features.netcoreapp3.0.cs => Microsoft.AspNetCore.Http.Features.netcoreapp.cs} (100%) rename src/Http/Http/ref/{Microsoft.AspNetCore.Http.netcoreapp3.0.cs => Microsoft.AspNetCore.Http.netcoreapp.cs} (100%) rename src/Http/Owin/ref/{Microsoft.AspNetCore.Owin.netcoreapp3.0.cs => Microsoft.AspNetCore.Owin.netcoreapp.cs} (100%) rename src/Http/Routing.Abstractions/ref/{Microsoft.AspNetCore.Routing.Abstractions.netcoreapp3.0.cs => Microsoft.AspNetCore.Routing.Abstractions.netcoreapp.cs} (100%) rename src/Http/Routing/ref/{Microsoft.AspNetCore.Routing.netcoreapp3.0.cs => Microsoft.AspNetCore.Routing.netcoreapp.cs} (100%) rename src/Http/WebUtilities/ref/{Microsoft.AspNetCore.WebUtilities.netcoreapp3.0.cs => Microsoft.AspNetCore.WebUtilities.netcoreapp.cs} (100%) rename src/Identity/Core/ref/{Microsoft.AspNetCore.Identity.netcoreapp3.0.cs => Microsoft.AspNetCore.Identity.netcoreapp.cs} (100%) rename src/Identity/EntityFrameworkCore/ref/{Microsoft.AspNetCore.Identity.EntityFrameworkCore.netcoreapp3.0.cs => Microsoft.AspNetCore.Identity.EntityFrameworkCore.netcoreapp.cs} (100%) rename src/Identity/Extensions.Core/ref/{Microsoft.Extensions.Identity.Core.netcoreapp3.0.cs => Microsoft.Extensions.Identity.Core.netcoreapp.cs} (100%) rename src/Identity/Extensions.Stores/ref/{Microsoft.Extensions.Identity.Stores.netcoreapp3.0.cs => Microsoft.Extensions.Identity.Stores.netcoreapp.cs} (100%) rename src/Identity/UI/ref/{Microsoft.AspNetCore.Identity.UI.netcoreapp3.0.cs => Microsoft.AspNetCore.Identity.UI.netcoreapp.cs} (100%) rename src/Middleware/CORS/ref/{Microsoft.AspNetCore.Cors.netcoreapp3.0.cs => Microsoft.AspNetCore.Cors.netcoreapp.cs} (100%) rename src/Middleware/ConcurrencyLimiter/ref/{Microsoft.AspNetCore.ConcurrencyLimiter.netcoreapp3.0.cs => Microsoft.AspNetCore.ConcurrencyLimiter.netcoreapp.cs} (100%) rename src/Middleware/Diagnostics.Abstractions/ref/{Microsoft.AspNetCore.Diagnostics.Abstractions.netcoreapp3.0.cs => Microsoft.AspNetCore.Diagnostics.Abstractions.netcoreapp.cs} (100%) rename src/Middleware/Diagnostics.EntityFrameworkCore/ref/{Microsoft.AspNetCore.Diagnostics.EntityFrameworkCore.netcoreapp3.0.cs => Microsoft.AspNetCore.Diagnostics.EntityFrameworkCore.netcoreapp.cs} (100%) rename src/Middleware/Diagnostics/ref/{Microsoft.AspNetCore.Diagnostics.netcoreapp3.0.cs => Microsoft.AspNetCore.Diagnostics.netcoreapp.cs} (100%) rename src/Middleware/HeaderPropagation/ref/{Microsoft.AspNetCore.HeaderPropagation.netcoreapp3.0.cs => Microsoft.AspNetCore.HeaderPropagation.netcoreapp.cs} (100%) rename src/Middleware/HealthChecks/ref/{Microsoft.AspNetCore.Diagnostics.HealthChecks.netcoreapp3.0.cs => Microsoft.AspNetCore.Diagnostics.HealthChecks.netcoreapp.cs} (100%) rename src/Middleware/HostFiltering/ref/{Microsoft.AspNetCore.HostFiltering.netcoreapp3.0.cs => Microsoft.AspNetCore.HostFiltering.netcoreapp.cs} (100%) rename src/Middleware/HttpOverrides/ref/{Microsoft.AspNetCore.HttpOverrides.netcoreapp3.0.cs => Microsoft.AspNetCore.HttpOverrides.netcoreapp.cs} (100%) rename src/Middleware/HttpsPolicy/ref/{Microsoft.AspNetCore.HttpsPolicy.netcoreapp3.0.cs => Microsoft.AspNetCore.HttpsPolicy.netcoreapp.cs} (100%) rename src/Middleware/Localization.Routing/ref/{Microsoft.AspNetCore.Localization.Routing.netcoreapp3.0.cs => Microsoft.AspNetCore.Localization.Routing.netcoreapp.cs} (100%) rename src/Middleware/Localization/ref/{Microsoft.AspNetCore.Localization.netcoreapp3.0.cs => Microsoft.AspNetCore.Localization.netcoreapp.cs} (100%) rename src/Middleware/MiddlewareAnalysis/ref/{Microsoft.AspNetCore.MiddlewareAnalysis.netcoreapp3.0.cs => Microsoft.AspNetCore.MiddlewareAnalysis.netcoreapp.cs} (100%) rename src/Middleware/NodeServices/ref/{Microsoft.AspNetCore.NodeServices.netcoreapp3.0.cs => Microsoft.AspNetCore.NodeServices.netcoreapp.cs} (100%) rename src/Middleware/ResponseCaching.Abstractions/ref/{Microsoft.AspNetCore.ResponseCaching.Abstractions.netcoreapp3.0.cs => Microsoft.AspNetCore.ResponseCaching.Abstractions.netcoreapp.cs} (100%) rename src/Middleware/ResponseCaching/ref/{Microsoft.AspNetCore.ResponseCaching.netcoreapp3.0.cs => Microsoft.AspNetCore.ResponseCaching.netcoreapp.cs} (100%) rename src/Middleware/ResponseCompression/ref/{Microsoft.AspNetCore.ResponseCompression.netcoreapp3.0.cs => Microsoft.AspNetCore.ResponseCompression.netcoreapp.cs} (100%) rename src/Middleware/Rewrite/ref/{Microsoft.AspNetCore.Rewrite.netcoreapp3.0.cs => Microsoft.AspNetCore.Rewrite.netcoreapp.cs} (100%) rename src/Middleware/Session/ref/{Microsoft.AspNetCore.Session.netcoreapp3.0.cs => Microsoft.AspNetCore.Session.netcoreapp.cs} (100%) rename src/Middleware/SpaServices.Extensions/ref/{Microsoft.AspNetCore.SpaServices.Extensions.netcoreapp3.0.cs => Microsoft.AspNetCore.SpaServices.Extensions.netcoreapp.cs} (100%) rename src/Middleware/SpaServices/ref/{Microsoft.AspNetCore.SpaServices.netcoreapp3.0.cs => Microsoft.AspNetCore.SpaServices.netcoreapp.cs} (100%) rename src/Middleware/StaticFiles/ref/{Microsoft.AspNetCore.StaticFiles.netcoreapp3.0.cs => Microsoft.AspNetCore.StaticFiles.netcoreapp.cs} (100%) rename src/Middleware/WebSockets/ref/{Microsoft.AspNetCore.WebSockets.netcoreapp3.0.cs => Microsoft.AspNetCore.WebSockets.netcoreapp.cs} (100%) rename src/Mvc/Mvc.Abstractions/ref/{Microsoft.AspNetCore.Mvc.Abstractions.netcoreapp3.0.cs => Microsoft.AspNetCore.Mvc.Abstractions.netcoreapp.cs} (100%) rename src/Mvc/Mvc.ApiExplorer/ref/{Microsoft.AspNetCore.Mvc.ApiExplorer.netcoreapp3.0.cs => Microsoft.AspNetCore.Mvc.ApiExplorer.netcoreapp.cs} (100%) rename src/Mvc/Mvc.Core/ref/{Microsoft.AspNetCore.Mvc.Core.netcoreapp3.0.cs => Microsoft.AspNetCore.Mvc.Core.netcoreapp.cs} (100%) rename src/Mvc/Mvc.Cors/ref/{Microsoft.AspNetCore.Mvc.Cors.netcoreapp3.0.cs => Microsoft.AspNetCore.Mvc.Cors.netcoreapp.cs} (100%) rename src/Mvc/Mvc.DataAnnotations/ref/{Microsoft.AspNetCore.Mvc.DataAnnotations.netcoreapp3.0.cs => Microsoft.AspNetCore.Mvc.DataAnnotations.netcoreapp.cs} (100%) rename src/Mvc/Mvc.Formatters.Json/ref/{Microsoft.AspNetCore.Mvc.Formatters.Json.netcoreapp3.0.cs => Microsoft.AspNetCore.Mvc.Formatters.Json.netcoreapp.cs} (100%) rename src/Mvc/Mvc.Formatters.Xml/ref/{Microsoft.AspNetCore.Mvc.Formatters.Xml.netcoreapp3.0.cs => Microsoft.AspNetCore.Mvc.Formatters.Xml.netcoreapp.cs} (100%) rename src/Mvc/Mvc.Localization/ref/{Microsoft.AspNetCore.Mvc.Localization.netcoreapp3.0.cs => Microsoft.AspNetCore.Mvc.Localization.netcoreapp.cs} (100%) rename src/Mvc/Mvc.NewtonsoftJson/ref/{Microsoft.AspNetCore.Mvc.NewtonsoftJson.netcoreapp3.0.cs => Microsoft.AspNetCore.Mvc.NewtonsoftJson.netcoreapp.cs} (100%) rename src/Mvc/Mvc.Razor.RuntimeCompilation/ref/{Microsoft.AspNetCore.Mvc.Razor.RuntimeCompilation.netcoreapp3.0.cs => Microsoft.AspNetCore.Mvc.Razor.RuntimeCompilation.netcoreapp.cs} (100%) rename src/Mvc/Mvc.Razor.RuntimeCompilation/src/build/{netcoreapp3.0 => netcoreapp5.0}/Microsoft.AspNetCore.Mvc.Razor.RuntimeCompilation.targets (100%) rename src/Mvc/Mvc.Razor/ref/{Microsoft.AspNetCore.Mvc.Razor.netcoreapp3.0.cs => Microsoft.AspNetCore.Mvc.Razor.netcoreapp.cs} (100%) rename src/Mvc/Mvc.RazorPages/ref/{Microsoft.AspNetCore.Mvc.RazorPages.netcoreapp3.0.cs => Microsoft.AspNetCore.Mvc.RazorPages.netcoreapp.cs} (100%) rename src/Mvc/Mvc.RazorPages/ref/{Microsoft.AspNetCore.Mvc.RazorPages.netcoreapp3.0.Manual.cs => Microsoft.AspNetCore.Mvc.RazorPages.netcoreapp5.0.Manual.cs} (100%) rename src/Mvc/Mvc.TagHelpers/ref/{Microsoft.AspNetCore.Mvc.TagHelpers.netcoreapp3.0.cs => Microsoft.AspNetCore.Mvc.TagHelpers.netcoreapp.cs} (100%) rename src/Mvc/Mvc.Testing/ref/{Microsoft.AspNetCore.Mvc.Testing.netcoreapp3.0.cs => Microsoft.AspNetCore.Mvc.Testing.netcoreapp.cs} (100%) rename src/Mvc/Mvc.ViewFeatures/ref/{Microsoft.AspNetCore.Mvc.ViewFeatures.netcoreapp3.0.cs => Microsoft.AspNetCore.Mvc.ViewFeatures.netcoreapp.cs} (100%) rename src/Mvc/Mvc/ref/{Microsoft.AspNetCore.Mvc.netcoreapp3.0.cs => Microsoft.AspNetCore.Mvc.netcoreapp.cs} (100%) rename src/Razor/Razor.Runtime/ref/{Microsoft.AspNetCore.Razor.Runtime.netcoreapp3.0.cs => Microsoft.AspNetCore.Razor.Runtime.netcoreapp.cs} (100%) rename src/Razor/Razor/ref/{Microsoft.AspNetCore.Razor.netcoreapp3.0.cs => Microsoft.AspNetCore.Razor.netcoreapp.cs} (100%) rename src/Security/Authentication/Certificate/ref/{Microsoft.AspNetCore.Authentication.Certificate.netcoreapp3.0.cs => Microsoft.AspNetCore.Authentication.Certificate.netcoreapp.cs} (100%) rename src/Security/Authentication/Cookies/ref/{Microsoft.AspNetCore.Authentication.Cookies.netcoreapp3.0.cs => Microsoft.AspNetCore.Authentication.Cookies.netcoreapp.cs} (100%) rename src/Security/Authentication/Core/ref/{Microsoft.AspNetCore.Authentication.netcoreapp3.0.cs => Microsoft.AspNetCore.Authentication.netcoreapp.cs} (100%) rename src/Security/Authentication/Facebook/ref/{Microsoft.AspNetCore.Authentication.Facebook.netcoreapp3.0.cs => Microsoft.AspNetCore.Authentication.Facebook.netcoreapp.cs} (100%) rename src/Security/Authentication/Google/ref/{Microsoft.AspNetCore.Authentication.Google.netcoreapp3.0.cs => Microsoft.AspNetCore.Authentication.Google.netcoreapp.cs} (100%) rename src/Security/Authentication/JwtBearer/ref/{Microsoft.AspNetCore.Authentication.JwtBearer.netcoreapp3.0.cs => Microsoft.AspNetCore.Authentication.JwtBearer.netcoreapp.cs} (100%) rename src/Security/Authentication/MicrosoftAccount/ref/{Microsoft.AspNetCore.Authentication.MicrosoftAccount.netcoreapp3.0.cs => Microsoft.AspNetCore.Authentication.MicrosoftAccount.netcoreapp.cs} (100%) rename src/Security/Authentication/Negotiate/ref/{Microsoft.AspNetCore.Authentication.Negotiate.netcoreapp3.0.cs => Microsoft.AspNetCore.Authentication.Negotiate.netcoreapp.cs} (100%) rename src/Security/Authentication/OAuth/ref/{Microsoft.AspNetCore.Authentication.OAuth.netcoreapp3.0.cs => Microsoft.AspNetCore.Authentication.OAuth.netcoreapp.cs} (100%) rename src/Security/Authentication/OpenIdConnect/ref/{Microsoft.AspNetCore.Authentication.OpenIdConnect.netcoreapp3.0.cs => Microsoft.AspNetCore.Authentication.OpenIdConnect.netcoreapp.cs} (100%) rename src/Security/Authentication/Twitter/ref/{Microsoft.AspNetCore.Authentication.Twitter.netcoreapp3.0.cs => Microsoft.AspNetCore.Authentication.Twitter.netcoreapp.cs} (100%) rename src/Security/Authentication/WsFederation/ref/{Microsoft.AspNetCore.Authentication.WsFederation.netcoreapp3.0.cs => Microsoft.AspNetCore.Authentication.WsFederation.netcoreapp.cs} (100%) rename src/Security/Authorization/Core/ref/{Microsoft.AspNetCore.Authorization.netcoreapp3.0.cs => Microsoft.AspNetCore.Authorization.netcoreapp.cs} (100%) rename src/Security/Authorization/Policy/ref/{Microsoft.AspNetCore.Authorization.Policy.netcoreapp3.0.cs => Microsoft.AspNetCore.Authorization.Policy.netcoreapp.cs} (100%) rename src/Security/CookiePolicy/ref/{Microsoft.AspNetCore.CookiePolicy.netcoreapp3.0.cs => Microsoft.AspNetCore.CookiePolicy.netcoreapp.cs} (100%) rename src/Servers/Connections.Abstractions/ref/{Microsoft.AspNetCore.Connections.Abstractions.netcoreapp3.0.cs => Microsoft.AspNetCore.Connections.Abstractions.netcoreapp.cs} (100%) rename src/Servers/HttpSys/ref/{Microsoft.AspNetCore.Server.HttpSys.netcoreapp3.0.cs => Microsoft.AspNetCore.Server.HttpSys.netcoreapp.cs} (100%) rename src/Servers/IIS/IIS/ref/{Microsoft.AspNetCore.Server.IIS.netcoreapp3.0.cs => Microsoft.AspNetCore.Server.IIS.netcoreapp.cs} (100%) rename src/Servers/IIS/IISIntegration/ref/{Microsoft.AspNetCore.Server.IISIntegration.netcoreapp3.0.cs => Microsoft.AspNetCore.Server.IISIntegration.netcoreapp.cs} (100%) rename src/Servers/Kestrel/Core/ref/{Microsoft.AspNetCore.Server.Kestrel.Core.netcoreapp3.0.cs => Microsoft.AspNetCore.Server.Kestrel.Core.netcoreapp.cs} (100%) rename src/Servers/Kestrel/Kestrel/ref/{Microsoft.AspNetCore.Server.Kestrel.netcoreapp3.0.cs => Microsoft.AspNetCore.Server.Kestrel.netcoreapp.cs} (100%) rename src/Servers/Kestrel/Transport.Libuv/ref/{Microsoft.AspNetCore.Server.Kestrel.Transport.Libuv.netcoreapp3.0.cs => Microsoft.AspNetCore.Server.Kestrel.Transport.Libuv.netcoreapp.cs} (100%) rename src/Servers/Kestrel/Transport.Sockets/ref/{Microsoft.AspNetCore.Server.Kestrel.Transport.Sockets.netcoreapp3.0.cs => Microsoft.AspNetCore.Server.Kestrel.Transport.Sockets.netcoreapp.cs} (100%) rename src/SignalR/clients/csharp/Client.Core/ref/{Microsoft.AspNetCore.SignalR.Client.Core.netcoreapp3.0.cs => Microsoft.AspNetCore.SignalR.Client.Core.netcoreapp.cs} (100%) rename src/SignalR/clients/csharp/Http.Connections.Client/ref/{Microsoft.AspNetCore.Http.Connections.Client.netcoreapp3.0.cs => Microsoft.AspNetCore.Http.Connections.Client.netcoreapp.cs} (100%) rename src/SignalR/common/Http.Connections.Common/ref/{Microsoft.AspNetCore.Http.Connections.Common.netcoreapp3.0.cs => Microsoft.AspNetCore.Http.Connections.Common.netcoreapp.cs} (100%) rename src/SignalR/common/Http.Connections/ref/{Microsoft.AspNetCore.Http.Connections.netcoreapp3.0.cs => Microsoft.AspNetCore.Http.Connections.netcoreapp.cs} (100%) rename src/SignalR/common/Protocols.Json/ref/{Microsoft.AspNetCore.SignalR.Protocols.Json.netcoreapp3.0.cs => Microsoft.AspNetCore.SignalR.Protocols.Json.netcoreapp.cs} (100%) rename src/SignalR/common/SignalR.Common/ref/{Microsoft.AspNetCore.SignalR.Common.netcoreapp3.0.cs => Microsoft.AspNetCore.SignalR.Common.netcoreapp.cs} (100%) rename src/SignalR/server/Core/ref/{Microsoft.AspNetCore.SignalR.Core.netcoreapp3.0.cs => Microsoft.AspNetCore.SignalR.Core.netcoreapp.cs} (100%) rename src/SignalR/server/SignalR/ref/{Microsoft.AspNetCore.SignalR.netcoreapp3.0.cs => Microsoft.AspNetCore.SignalR.netcoreapp.cs} (100%) rename src/SignalR/server/StackExchangeRedis/ref/{Microsoft.AspNetCore.SignalR.StackExchangeRedis.netcoreapp3.0.cs => Microsoft.AspNetCore.SignalR.StackExchangeRedis.netcoreapp.cs} (100%) diff --git a/.azure/pipelines/ci.yml b/.azure/pipelines/ci.yml index 9faf14391d..d9dadaaee6 100644 --- a/.azure/pipelines/ci.yml +++ b/.azure/pipelines/ci.yml @@ -504,16 +504,6 @@ jobs: chmod +x $HOME/bin/jq echo "##vso[task.prependpath]$HOME/bin" displayName: Install jq - - task: UseDotNet@2 - displayName: 'Use .NET Core sdk' - inputs: - packageType: sdk - # The SDK version selected here is intentionally supposed to use the latest release - # For the purpose of building Linux distros, we can't depend on features of the SDK - # which may not exist in pre-built versions of the SDK - version: 3.0.x - installationPath: $(DotNetCoreSdkDir) - includePreviewVersions: true - script: ./eng/scripts/ci-source-build.sh --ci --configuration Release /p:BuildManaged=true /p:BuildNodeJs=false displayName: Run ci-source-build.sh - task: PublishBuildArtifacts@1 diff --git a/Directory.Build.props b/Directory.Build.props index 4b87f1845a..76414deaaa 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -43,7 +43,7 @@ true - netcoreapp$(TFMNetCoreMajorVersion).$(TFMNetCoreMinorVersion) + netcoreapp5.0 diff --git a/eng/PublishSymbols.proj b/eng/PublishSymbols.proj index a33a1df232..ca5ba682f7 100644 --- a/eng/PublishSymbols.proj +++ b/eng/PublishSymbols.proj @@ -1,7 +1,7 @@ - netcoreapp3.0 + $(DefaultNetCoreTargetFramework) true artifacts\manifests\ true diff --git a/eng/SharedFramework.External.props b/eng/SharedFramework.External.props index 36ea360df2..8fdebc787d 100644 --- a/eng/SharedFramework.External.props +++ b/eng/SharedFramework.External.props @@ -107,7 +107,7 @@ These compilation references are necessary to workaround the mismatch of what is found in the ref pack for NETCore.App and what is actually present at runtime. See https://github.com/dotnet/corefx/issues/34906 --> - + <_CompilationOnlyReference Include="Microsoft.Win32.Registry" /> <_CompilationOnlyReference Include="System.Security.Cryptography.Cng" /> <_CompilationOnlyReference Include="System.Security.Principal.Windows" /> diff --git a/eng/Version.Details.xml b/eng/Version.Details.xml index a2b48e7e15..69bf0109bb 100644 --- a/eng/Version.Details.xml +++ b/eng/Version.Details.xml @@ -9,412 +9,412 @@ --> - + https://github.com/aspnet/Blazor - b2c48dd8c9099f71908fac26089cbea2c76d06a1 + 7eeab316fa122b69a9bd777c93dcc78bc6a68905 - + https://github.com/aspnet/AspNetCore-Tooling - 448a88e86d20fd9315901f663318d64c9c6841bf + e60ad5d5d050ab5ee87f4cf1fb9ae440db87777d - + https://github.com/aspnet/AspNetCore-Tooling - 448a88e86d20fd9315901f663318d64c9c6841bf + e60ad5d5d050ab5ee87f4cf1fb9ae440db87777d - + https://github.com/aspnet/AspNetCore-Tooling - 448a88e86d20fd9315901f663318d64c9c6841bf + e60ad5d5d050ab5ee87f4cf1fb9ae440db87777d - + https://github.com/aspnet/AspNetCore-Tooling - 448a88e86d20fd9315901f663318d64c9c6841bf + e60ad5d5d050ab5ee87f4cf1fb9ae440db87777d - + https://github.com/aspnet/EntityFrameworkCore - e33237ee4210a27d16747aba8dd655cabc808922 + 44d8c31bcb4bb46714389619070be9d1c6bad319 - + https://github.com/aspnet/EntityFrameworkCore - e33237ee4210a27d16747aba8dd655cabc808922 + 44d8c31bcb4bb46714389619070be9d1c6bad319 - + https://github.com/aspnet/EntityFrameworkCore - e33237ee4210a27d16747aba8dd655cabc808922 + 44d8c31bcb4bb46714389619070be9d1c6bad319 - + https://github.com/aspnet/EntityFrameworkCore - e33237ee4210a27d16747aba8dd655cabc808922 + 44d8c31bcb4bb46714389619070be9d1c6bad319 - + https://github.com/aspnet/EntityFrameworkCore - e33237ee4210a27d16747aba8dd655cabc808922 + 44d8c31bcb4bb46714389619070be9d1c6bad319 - + https://github.com/aspnet/EntityFrameworkCore - e33237ee4210a27d16747aba8dd655cabc808922 + 44d8c31bcb4bb46714389619070be9d1c6bad319 - + https://github.com/aspnet/EntityFrameworkCore - e33237ee4210a27d16747aba8dd655cabc808922 + 44d8c31bcb4bb46714389619070be9d1c6bad319 - + https://github.com/aspnet/Extensions - 14c51735aa12343b7b0e53ae775990ab9c98c42f + dbe3543601c1ab290fde1ae30ea8dd978d33d4d3 - + https://github.com/aspnet/Extensions - 14c51735aa12343b7b0e53ae775990ab9c98c42f + dbe3543601c1ab290fde1ae30ea8dd978d33d4d3 - + https://github.com/aspnet/Extensions - 14c51735aa12343b7b0e53ae775990ab9c98c42f + dbe3543601c1ab290fde1ae30ea8dd978d33d4d3 - + https://github.com/aspnet/Extensions - 14c51735aa12343b7b0e53ae775990ab9c98c42f + dbe3543601c1ab290fde1ae30ea8dd978d33d4d3 - + https://github.com/aspnet/Extensions - 14c51735aa12343b7b0e53ae775990ab9c98c42f + dbe3543601c1ab290fde1ae30ea8dd978d33d4d3 - + https://github.com/aspnet/Extensions - 14c51735aa12343b7b0e53ae775990ab9c98c42f + dbe3543601c1ab290fde1ae30ea8dd978d33d4d3 - + https://github.com/aspnet/Extensions - 14c51735aa12343b7b0e53ae775990ab9c98c42f + dbe3543601c1ab290fde1ae30ea8dd978d33d4d3 - + https://github.com/aspnet/Extensions - 14c51735aa12343b7b0e53ae775990ab9c98c42f + dbe3543601c1ab290fde1ae30ea8dd978d33d4d3 - + https://github.com/aspnet/Extensions - 14c51735aa12343b7b0e53ae775990ab9c98c42f + dbe3543601c1ab290fde1ae30ea8dd978d33d4d3 - + https://github.com/aspnet/Extensions - 14c51735aa12343b7b0e53ae775990ab9c98c42f + dbe3543601c1ab290fde1ae30ea8dd978d33d4d3 - + https://github.com/aspnet/Extensions - 14c51735aa12343b7b0e53ae775990ab9c98c42f + dbe3543601c1ab290fde1ae30ea8dd978d33d4d3 - + https://github.com/aspnet/Extensions - 14c51735aa12343b7b0e53ae775990ab9c98c42f + dbe3543601c1ab290fde1ae30ea8dd978d33d4d3 - + https://github.com/aspnet/Extensions - 14c51735aa12343b7b0e53ae775990ab9c98c42f + dbe3543601c1ab290fde1ae30ea8dd978d33d4d3 - + https://github.com/aspnet/Extensions - 14c51735aa12343b7b0e53ae775990ab9c98c42f + dbe3543601c1ab290fde1ae30ea8dd978d33d4d3 - + https://github.com/aspnet/Extensions - 14c51735aa12343b7b0e53ae775990ab9c98c42f + dbe3543601c1ab290fde1ae30ea8dd978d33d4d3 - + https://github.com/aspnet/Extensions - 14c51735aa12343b7b0e53ae775990ab9c98c42f + dbe3543601c1ab290fde1ae30ea8dd978d33d4d3 - + https://github.com/aspnet/Extensions - 14c51735aa12343b7b0e53ae775990ab9c98c42f + dbe3543601c1ab290fde1ae30ea8dd978d33d4d3 - + https://github.com/aspnet/Extensions - 14c51735aa12343b7b0e53ae775990ab9c98c42f + dbe3543601c1ab290fde1ae30ea8dd978d33d4d3 - + https://github.com/aspnet/Extensions - 14c51735aa12343b7b0e53ae775990ab9c98c42f + dbe3543601c1ab290fde1ae30ea8dd978d33d4d3 - + https://github.com/aspnet/Extensions - 14c51735aa12343b7b0e53ae775990ab9c98c42f + dbe3543601c1ab290fde1ae30ea8dd978d33d4d3 - + https://github.com/aspnet/Extensions - 14c51735aa12343b7b0e53ae775990ab9c98c42f + dbe3543601c1ab290fde1ae30ea8dd978d33d4d3 - + https://github.com/aspnet/Extensions - 14c51735aa12343b7b0e53ae775990ab9c98c42f + dbe3543601c1ab290fde1ae30ea8dd978d33d4d3 - + https://github.com/aspnet/Extensions - 14c51735aa12343b7b0e53ae775990ab9c98c42f + dbe3543601c1ab290fde1ae30ea8dd978d33d4d3 - + https://github.com/aspnet/Extensions - 14c51735aa12343b7b0e53ae775990ab9c98c42f + dbe3543601c1ab290fde1ae30ea8dd978d33d4d3 - + https://github.com/aspnet/Extensions - 14c51735aa12343b7b0e53ae775990ab9c98c42f + dbe3543601c1ab290fde1ae30ea8dd978d33d4d3 - + https://github.com/aspnet/Extensions - 14c51735aa12343b7b0e53ae775990ab9c98c42f + dbe3543601c1ab290fde1ae30ea8dd978d33d4d3 - + https://github.com/aspnet/Extensions - 14c51735aa12343b7b0e53ae775990ab9c98c42f + dbe3543601c1ab290fde1ae30ea8dd978d33d4d3 - + https://github.com/aspnet/Extensions - 14c51735aa12343b7b0e53ae775990ab9c98c42f + dbe3543601c1ab290fde1ae30ea8dd978d33d4d3 - + https://github.com/aspnet/Extensions - 14c51735aa12343b7b0e53ae775990ab9c98c42f + dbe3543601c1ab290fde1ae30ea8dd978d33d4d3 - + https://github.com/aspnet/Extensions - 14c51735aa12343b7b0e53ae775990ab9c98c42f + dbe3543601c1ab290fde1ae30ea8dd978d33d4d3 - + https://github.com/aspnet/Extensions - 14c51735aa12343b7b0e53ae775990ab9c98c42f + dbe3543601c1ab290fde1ae30ea8dd978d33d4d3 - + https://github.com/aspnet/Extensions - 14c51735aa12343b7b0e53ae775990ab9c98c42f + dbe3543601c1ab290fde1ae30ea8dd978d33d4d3 - + https://github.com/aspnet/Extensions - 14c51735aa12343b7b0e53ae775990ab9c98c42f + dbe3543601c1ab290fde1ae30ea8dd978d33d4d3 - + https://github.com/aspnet/Extensions - 14c51735aa12343b7b0e53ae775990ab9c98c42f + dbe3543601c1ab290fde1ae30ea8dd978d33d4d3 - + https://github.com/aspnet/Extensions - 14c51735aa12343b7b0e53ae775990ab9c98c42f + dbe3543601c1ab290fde1ae30ea8dd978d33d4d3 - + https://github.com/aspnet/Extensions - 14c51735aa12343b7b0e53ae775990ab9c98c42f + dbe3543601c1ab290fde1ae30ea8dd978d33d4d3 - + https://github.com/aspnet/Extensions - 14c51735aa12343b7b0e53ae775990ab9c98c42f + dbe3543601c1ab290fde1ae30ea8dd978d33d4d3 - + https://github.com/aspnet/Extensions - 14c51735aa12343b7b0e53ae775990ab9c98c42f + dbe3543601c1ab290fde1ae30ea8dd978d33d4d3 - + https://github.com/aspnet/Extensions - 14c51735aa12343b7b0e53ae775990ab9c98c42f + dbe3543601c1ab290fde1ae30ea8dd978d33d4d3 - + https://github.com/aspnet/Extensions - 14c51735aa12343b7b0e53ae775990ab9c98c42f + dbe3543601c1ab290fde1ae30ea8dd978d33d4d3 - + https://github.com/aspnet/Extensions - 14c51735aa12343b7b0e53ae775990ab9c98c42f + dbe3543601c1ab290fde1ae30ea8dd978d33d4d3 - + https://github.com/aspnet/Extensions - 14c51735aa12343b7b0e53ae775990ab9c98c42f + dbe3543601c1ab290fde1ae30ea8dd978d33d4d3 - + https://github.com/aspnet/Extensions - 14c51735aa12343b7b0e53ae775990ab9c98c42f + dbe3543601c1ab290fde1ae30ea8dd978d33d4d3 - + https://github.com/aspnet/Extensions - 14c51735aa12343b7b0e53ae775990ab9c98c42f + dbe3543601c1ab290fde1ae30ea8dd978d33d4d3 - + https://github.com/aspnet/Extensions - 14c51735aa12343b7b0e53ae775990ab9c98c42f + dbe3543601c1ab290fde1ae30ea8dd978d33d4d3 - + https://github.com/aspnet/Extensions - 14c51735aa12343b7b0e53ae775990ab9c98c42f + dbe3543601c1ab290fde1ae30ea8dd978d33d4d3 - + https://github.com/aspnet/Extensions - 14c51735aa12343b7b0e53ae775990ab9c98c42f + dbe3543601c1ab290fde1ae30ea8dd978d33d4d3 - + https://github.com/aspnet/Extensions - 14c51735aa12343b7b0e53ae775990ab9c98c42f + dbe3543601c1ab290fde1ae30ea8dd978d33d4d3 - + https://github.com/aspnet/Extensions - 14c51735aa12343b7b0e53ae775990ab9c98c42f + dbe3543601c1ab290fde1ae30ea8dd978d33d4d3 - + https://github.com/aspnet/Extensions - 14c51735aa12343b7b0e53ae775990ab9c98c42f + dbe3543601c1ab290fde1ae30ea8dd978d33d4d3 - + https://github.com/aspnet/Extensions - 14c51735aa12343b7b0e53ae775990ab9c98c42f + dbe3543601c1ab290fde1ae30ea8dd978d33d4d3 - + https://github.com/aspnet/Extensions - 14c51735aa12343b7b0e53ae775990ab9c98c42f + dbe3543601c1ab290fde1ae30ea8dd978d33d4d3 - + https://github.com/aspnet/Extensions - 14c51735aa12343b7b0e53ae775990ab9c98c42f + dbe3543601c1ab290fde1ae30ea8dd978d33d4d3 - + https://github.com/aspnet/Extensions - 14c51735aa12343b7b0e53ae775990ab9c98c42f + dbe3543601c1ab290fde1ae30ea8dd978d33d4d3 - + https://github.com/aspnet/Extensions - 14c51735aa12343b7b0e53ae775990ab9c98c42f + dbe3543601c1ab290fde1ae30ea8dd978d33d4d3 - + https://github.com/aspnet/Extensions - 14c51735aa12343b7b0e53ae775990ab9c98c42f + dbe3543601c1ab290fde1ae30ea8dd978d33d4d3 - + https://github.com/aspnet/Extensions - 14c51735aa12343b7b0e53ae775990ab9c98c42f + dbe3543601c1ab290fde1ae30ea8dd978d33d4d3 - + https://github.com/aspnet/Extensions - 14c51735aa12343b7b0e53ae775990ab9c98c42f + dbe3543601c1ab290fde1ae30ea8dd978d33d4d3 - + https://github.com/aspnet/Extensions - 14c51735aa12343b7b0e53ae775990ab9c98c42f + dbe3543601c1ab290fde1ae30ea8dd978d33d4d3 - + https://github.com/dotnet/corefx - b82d2bc44424c8a99a1f0fc13202bdfd43e6f9f5 + a6c1d1ac235aba5bf17dd06228a62509192cda6a - + https://github.com/dotnet/corefx - b82d2bc44424c8a99a1f0fc13202bdfd43e6f9f5 + a6c1d1ac235aba5bf17dd06228a62509192cda6a - + https://github.com/dotnet/corefx - b82d2bc44424c8a99a1f0fc13202bdfd43e6f9f5 + a6c1d1ac235aba5bf17dd06228a62509192cda6a - + https://github.com/dotnet/corefx - b82d2bc44424c8a99a1f0fc13202bdfd43e6f9f5 + a6c1d1ac235aba5bf17dd06228a62509192cda6a - + https://github.com/dotnet/corefx - b82d2bc44424c8a99a1f0fc13202bdfd43e6f9f5 + a6c1d1ac235aba5bf17dd06228a62509192cda6a https://github.com/dotnet/corefx a28176b5ec68b6da1472934fe9493790d1665cae - + https://github.com/dotnet/corefx - b82d2bc44424c8a99a1f0fc13202bdfd43e6f9f5 + a6c1d1ac235aba5bf17dd06228a62509192cda6a - + https://github.com/dotnet/corefx - b82d2bc44424c8a99a1f0fc13202bdfd43e6f9f5 + a6c1d1ac235aba5bf17dd06228a62509192cda6a - + https://github.com/dotnet/corefx - b82d2bc44424c8a99a1f0fc13202bdfd43e6f9f5 + a6c1d1ac235aba5bf17dd06228a62509192cda6a - + https://github.com/dotnet/corefx - b82d2bc44424c8a99a1f0fc13202bdfd43e6f9f5 + a6c1d1ac235aba5bf17dd06228a62509192cda6a - + https://github.com/dotnet/corefx - b82d2bc44424c8a99a1f0fc13202bdfd43e6f9f5 + a6c1d1ac235aba5bf17dd06228a62509192cda6a - + https://github.com/dotnet/corefx - b82d2bc44424c8a99a1f0fc13202bdfd43e6f9f5 + a6c1d1ac235aba5bf17dd06228a62509192cda6a - + https://github.com/dotnet/corefx - b82d2bc44424c8a99a1f0fc13202bdfd43e6f9f5 + a6c1d1ac235aba5bf17dd06228a62509192cda6a - + https://github.com/dotnet/corefx - b82d2bc44424c8a99a1f0fc13202bdfd43e6f9f5 + a6c1d1ac235aba5bf17dd06228a62509192cda6a - + https://github.com/dotnet/corefx - b82d2bc44424c8a99a1f0fc13202bdfd43e6f9f5 + a6c1d1ac235aba5bf17dd06228a62509192cda6a - + https://github.com/dotnet/corefx - b82d2bc44424c8a99a1f0fc13202bdfd43e6f9f5 + a6c1d1ac235aba5bf17dd06228a62509192cda6a - + https://github.com/dotnet/corefx - b82d2bc44424c8a99a1f0fc13202bdfd43e6f9f5 + a6c1d1ac235aba5bf17dd06228a62509192cda6a - + https://github.com/dotnet/corefx - b82d2bc44424c8a99a1f0fc13202bdfd43e6f9f5 + a6c1d1ac235aba5bf17dd06228a62509192cda6a - + https://github.com/dotnet/corefx - b82d2bc44424c8a99a1f0fc13202bdfd43e6f9f5 + a6c1d1ac235aba5bf17dd06228a62509192cda6a - + https://github.com/dotnet/corefx - b82d2bc44424c8a99a1f0fc13202bdfd43e6f9f5 + a6c1d1ac235aba5bf17dd06228a62509192cda6a - + https://github.com/dotnet/corefx - b82d2bc44424c8a99a1f0fc13202bdfd43e6f9f5 + a6c1d1ac235aba5bf17dd06228a62509192cda6a - + https://github.com/dotnet/corefx - b82d2bc44424c8a99a1f0fc13202bdfd43e6f9f5 + a6c1d1ac235aba5bf17dd06228a62509192cda6a - + https://github.com/dotnet/corefx - b82d2bc44424c8a99a1f0fc13202bdfd43e6f9f5 + a6c1d1ac235aba5bf17dd06228a62509192cda6a - + https://github.com/dotnet/core-setup - 9408f55c19a0a9118a23b05f83200a3534dd9f7f + 62a9f1bdf39ba0b719875d33e248408d3802e925 - + https://github.com/dotnet/core-setup - 9408f55c19a0a9118a23b05f83200a3534dd9f7f + 62a9f1bdf39ba0b719875d33e248408d3802e925 - + https://github.com/dotnet/core-setup - 9408f55c19a0a9118a23b05f83200a3534dd9f7f + 62a9f1bdf39ba0b719875d33e248408d3802e925 - + https://github.com/dotnet/core-setup - 9408f55c19a0a9118a23b05f83200a3534dd9f7f + 62a9f1bdf39ba0b719875d33e248408d3802e925 - + https://github.com/dotnet/corefx - b82d2bc44424c8a99a1f0fc13202bdfd43e6f9f5 + a6c1d1ac235aba5bf17dd06228a62509192cda6a - + https://github.com/aspnet/Extensions - 14c51735aa12343b7b0e53ae775990ab9c98c42f + dbe3543601c1ab290fde1ae30ea8dd978d33d4d3 https://github.com/dotnet/arcade @@ -428,13 +428,13 @@ https://github.com/dotnet/arcade a11024c8c22cc762011addafc30c653c938048f4 - + https://github.com/aspnet/Extensions - 14c51735aa12343b7b0e53ae775990ab9c98c42f + dbe3543601c1ab290fde1ae30ea8dd978d33d4d3 - + https://github.com/dotnet/roslyn - ed92d532473db83c1db47b313ee1c1bd7520aa08 + a1905991543bed104f7f7f0842aca2b65d263b87 diff --git a/eng/Versions.props b/eng/Versions.props index 9141b1a63c..c3b3b87f7b 100644 --- a/eng/Versions.props +++ b/eng/Versions.props @@ -38,11 +38,6 @@ false - - - 3 - 0 - 1.0.0-beta.19411.1 - 3.3.0-beta3-19409-05 + 3.3.0-beta3-19406-05 - 3.0.0-preview9-19410-12 - 3.0.0-preview9-19410-12 - 3.0.0-preview9-19410-12 - 2.1.0-preview9-19410-12 + 5.0.0-alpha1.19404.5 + 5.0.0-alpha1.19404.5 + 5.0.0-alpha1.19404.5 + 2.1.0-alpha1.19404.5 - 1.0.0-preview9.19409.17 - 4.6.0-preview9.19409.17 - 4.6.0-preview9.19409.17 - 4.6.0-preview9.19409.17 - 4.6.0-preview9.19409.17 + 1.1.0-alpha1.19381.2 + 5.0.0-alpha1.19381.2 + 5.0.0-alpha1.19381.2 + 5.0.0-alpha1.19381.2 + 5.0.0-alpha1.19381.2 4.7.0-preview6.19264.9 - 4.6.0-preview9.19409.17 - 4.6.0-preview9.19409.17 - 4.6.0-preview9.19409.17 - 4.6.0-preview9.19409.17 - 4.6.0-preview9.19409.17 - 1.7.0-preview9.19409.17 - 4.6.0-preview9.19409.17 - 4.6.0-preview9.19409.17 - 4.6.0-preview9.19409.17 - 4.6.0-preview9.19409.17 - 4.6.0-preview9.19409.17 - 4.6.0-preview9.19409.17 - 4.6.0-preview9.19409.17 - 4.6.0-preview9.19409.17 - 4.6.0-preview9.19409.17 - 4.6.0-preview9.19409.17 - 4.6.0-preview9.19409.17 + 5.0.0-alpha1.19381.2 + 5.0.0-alpha1.19381.2 + 5.0.0-alpha1.19381.2 + 5.0.0-alpha1.19381.2 + 5.0.0-alpha1.19381.2 + 1.8.0-alpha1.19381.2 + 5.0.0-alpha1.19381.2 + 5.0.0-alpha1.19381.2 + 5.0.0-alpha1.19381.2 + 5.0.0-alpha1.19381.2 + 5.0.0-alpha1.19381.2 + 5.0.0-alpha1.19381.2 + 5.0.0-alpha1.19381.2 + 5.0.0-alpha1.19381.2 + 5.0.0-alpha1.19381.2 + 5.0.0-alpha1.19381.2 + 5.0.0-alpha1.19381.2 - 3.0.0-preview9.19409.17 + 5.0.0-alpha1.19381.2 - 5.0.0-alpha1.19405.2 + 5.0.0-alpha1.19426.2 - 3.0.0-preview9.19411.2 - 3.0.0-preview9.19411.2 - 3.0.0-preview9.19411.2 - 3.0.0-preview9.19411.2 - 3.0.0-preview9.19411.2 - 3.0.0-preview9.19411.2 - 3.0.0-preview9.19411.2 - 3.0.0-preview9.19411.2 - 3.0.0-preview9.19411.2 - 3.0.0-preview9.19411.2 - 3.0.0-preview9.19411.2 - 3.0.0-preview9.19411.2 - 3.0.0-preview9.19411.2 - 3.0.0-preview9.19411.2 - 3.0.0-preview9.19411.2 - 3.0.0-preview9.19411.2 - 3.0.0-preview9.19411.2 - 3.0.0-preview9.19411.2 - 3.0.0-preview9.19411.2 - 3.0.0-preview9.19411.2 - 3.0.0-preview9.19411.2 - 3.0.0-preview9.19411.2 - 3.0.0-preview9.19411.2 - 3.0.0-preview9.19411.2 - 3.0.0-preview9.19411.2 - 3.0.0-preview9.19411.2 - 3.0.0-preview9.19411.2 - 3.0.0-preview9.19411.2 - 3.0.0-preview9.19411.2 - 3.0.0-preview9.19411.2 - 3.0.0-preview9.19411.2 - 3.0.0-preview9.19411.2 - 3.0.0-preview9.19411.2 - 3.0.0-preview9.19411.2 - 3.0.0-preview9.19411.2 - 3.0.0-preview9.19411.2 - 3.0.0-preview9.19411.2 - 3.0.0-preview9.19411.2 - 3.0.0-preview9.19411.2 - 3.0.0-preview9.19411.2 - 3.0.0-preview9.19411.2 - 3.0.0-preview9.19411.2 - 3.0.0-preview9.19411.2 - 3.0.0-preview9.19411.2 - 3.0.0-preview9.19411.2 - 3.0.0-preview9.19411.2 - 3.0.0-preview9.19411.2 - 3.0.0-preview9.19411.2 - 3.0.0-preview9.19411.2 - 3.0.0-preview9.19411.2 - 3.0.0-preview9.19411.2 - 3.0.0-preview9.19411.2 - 3.0.0-preview9.19411.2 - 3.0.0-preview9.19411.2 - 3.0.0-preview9.19411.2 - 3.0.0-preview9.19411.2 - 3.0.0-preview9.19411.2 - 3.0.0-preview9.19411.2 - 3.0.0-preview9.19411.2 - 3.0.0-preview9.19411.2 - 3.0.0-preview9.19411.2 + 5.0.0-alpha1.19406.3 + 5.0.0-alpha1.19406.3 + 5.0.0-alpha1.19406.3 + 5.0.0-alpha1.19406.3 + 5.0.0-alpha1.19406.3 + 5.0.0-alpha1.19406.3 + 5.0.0-alpha1.19406.3 + 5.0.0-alpha1.19406.3 + 5.0.0-alpha1.19406.3 + 5.0.0-alpha1.19406.3 + 5.0.0-alpha1.19406.3 + 5.0.0-alpha1.19406.3 + 5.0.0-alpha1.19406.3 + 5.0.0-alpha1.19406.3 + 5.0.0-alpha1.19406.3 + 5.0.0-alpha1.19406.3 + 5.0.0-alpha1.19406.3 + 5.0.0-alpha1.19406.3 + 5.0.0-alpha1.19406.3 + 5.0.0-alpha1.19406.3 + 5.0.0-alpha1.19406.3 + 5.0.0-alpha1.19406.3 + 5.0.0-alpha1.19406.3 + 5.0.0-alpha1.19406.3 + 5.0.0-alpha1.19406.3 + 5.0.0-alpha1.19406.3 + 5.0.0-alpha1.19406.3 + 5.0.0-alpha1.19406.3 + 5.0.0-alpha1.19406.3 + 5.0.0-alpha1.19406.3 + 5.0.0-alpha1.19406.3 + 5.0.0-alpha1.19406.3 + 5.0.0-alpha1.19406.3 + 5.0.0-alpha1.19406.3 + 5.0.0-alpha1.19406.3 + 5.0.0-alpha1.19406.3 + 5.0.0-alpha1.19406.3 + 5.0.0-alpha1.19406.3 + 5.0.0-alpha1.19406.3 + 5.0.0-alpha1.19406.3 + 5.0.0-alpha1.19406.3 + 5.0.0-alpha1.19406.3 + 5.0.0-alpha1.19406.3 + 5.0.0-alpha1.19406.3 + 5.0.0-alpha1.19406.3 + 5.0.0-alpha1.19406.3 + 5.0.0-alpha1.19406.3 + 5.0.0-alpha1.19406.3 + 5.0.0-alpha1.19406.3 + 5.0.0-alpha1.19406.3 + 5.0.0-alpha1.19406.3 + 5.0.0-alpha1.19406.3 + 5.0.0-alpha1.19406.3 + 5.0.0-alpha1.19406.3 + 5.0.0-alpha1.19406.3 + 5.0.0-alpha1.19406.3 + 5.0.0-alpha1.19406.3 + 5.0.0-alpha1.19406.3 + 5.0.0-alpha1.19406.3 + 5.0.0-alpha1.19406.3 + 5.0.0-alpha1.19406.3 - 3.0.0-preview9.19412.3 - 3.0.0-preview9.19412.3 - 3.0.0-preview9.19412.3 - 3.0.0-preview9.19412.3 - 3.0.0-preview9.19412.3 - 3.0.0-preview9.19412.3 - 3.0.0-preview9.19412.3 + 5.0.0-alpha1.19407.3 + 5.0.0-alpha1.19407.3 + 5.0.0-alpha1.19407.3 + 5.0.0-alpha1.19407.3 + 5.0.0-alpha1.19407.3 + 5.0.0-alpha1.19407.3 + 5.0.0-alpha1.19407.3 - 5.0.0-alpha1.19407.1 - 5.0.0-alpha1.19407.1 - 5.0.0-alpha1.19407.1 - 5.0.0-alpha1.19407.1 + 5.0.0-alpha1.19426.2 + 5.0.0-alpha1.19426.2 + 5.0.0-alpha1.19426.2 + 5.0.0-alpha1.19426.2 - - $(MicrosoftNETCorePlatformsPackageVersion) - - "$reporoot/global.json" - - # Restore the original global.json file -trap "{ - mv "$reporoot/global.bak.json" "$reporoot/global.json" -}" EXIT - export DotNetBuildFromSource='true' # Build repo tasks diff --git a/eng/targets/ReferenceAssembly.targets b/eng/targets/ReferenceAssembly.targets index bb6230e633..46225a45e1 100644 --- a/eng/targets/ReferenceAssembly.targets +++ b/eng/targets/ReferenceAssembly.targets @@ -45,8 +45,11 @@ + <_RefSourceFileTFM>$(TargetFramework) + <_RefSourceFileTFM Condition="$(TargetFramework.StartsWith('netcoreapp'))">netcoreapp + <_RefSourceOutputPath>$([System.IO.Directory]::GetParent('$(MSBuildProjectDirectory)'))/ref/ - <_RefSourceFileName>$(AssemblyName).$(TargetFramework).cs + <_RefSourceFileName>$(AssemblyName).$(_RefSourceFileTFM).cs <_RefSourceFileOutputPath>$(_RefSourceOutputPath)$(_RefSourceFileName) diff --git a/eng/tools/BaselineGenerator/BaselineGenerator.csproj b/eng/tools/BaselineGenerator/BaselineGenerator.csproj index 79fd9175cf..082a9eaf02 100644 --- a/eng/tools/BaselineGenerator/BaselineGenerator.csproj +++ b/eng/tools/BaselineGenerator/BaselineGenerator.csproj @@ -2,7 +2,7 @@ Exe - netcoreapp3.0 + $(DefaultNetCoreTargetFramework) -s https://dotnetfeed.blob.core.windows.net/dotnet-core/index.json $(MSBuildThisFileDirectory)../../ diff --git a/eng/tools/Maestro/Maestro.csproj b/eng/tools/Maestro/Maestro.csproj index 67a5210bc4..2ca58dd794 100644 --- a/eng/tools/Maestro/Maestro.csproj +++ b/eng/tools/Maestro/Maestro.csproj @@ -2,7 +2,7 @@ - netcoreapp3.0 + $(DefaultNetCoreTargetFramework) true $(ArtifactsDir)manifests\ true diff --git a/eng/tools/RepoTasks/RepoTasks.csproj b/eng/tools/RepoTasks/RepoTasks.csproj index 0bf6be98ae..5bef776106 100644 --- a/eng/tools/RepoTasks/RepoTasks.csproj +++ b/eng/tools/RepoTasks/RepoTasks.csproj @@ -1,7 +1,7 @@ - netcoreapp3.0 + $(DefaultNetCoreTargetFramework) $(TargetFrameworks);net472 $(DefineConstants);BUILD_MSI_TASKS false @@ -16,7 +16,7 @@ - + diff --git a/eng/tools/RepoTasks/RepoTasks.tasks b/eng/tools/RepoTasks/RepoTasks.tasks index b55f394e4c..0fa015d81f 100644 --- a/eng/tools/RepoTasks/RepoTasks.tasks +++ b/eng/tools/RepoTasks/RepoTasks.tasks @@ -1,6 +1,6 @@ - <_RepoTaskAssemblyFolder Condition="'$(MSBuildRuntimeType)' == 'core'">netcoreapp3.0 + <_RepoTaskAssemblyFolder Condition="'$(MSBuildRuntimeType)' == 'core'">netcoreapp5.0 <_RepoTaskAssemblyFolder Condition="'$(MSBuildRuntimeType)' != 'core'">net472 <_RepoTaskAssembly>$(ArtifactsBinDir)RepoTasks\Release\$(_RepoTaskAssemblyFolder)\RepoTasks.dll diff --git a/global.json b/global.json index 2d26e775e7..f48e76cc34 100644 --- a/global.json +++ b/global.json @@ -1,9 +1,9 @@ { "sdk": { - "version": "3.0.100-preview7-012821" + "version": "5.0.100-alpha1-013788" }, "tools": { - "dotnet": "3.0.100-preview7-012821", + "dotnet": "5.0.100-alpha1-013788", "runtimes": { "dotnet/x64": [ "$(MicrosoftNETCoreAppRuntimeVersion)" diff --git a/src/Analyzers/Analyzers/test/Microsoft.AspNetCore.Analyzers.Test.csproj b/src/Analyzers/Analyzers/test/Microsoft.AspNetCore.Analyzers.Test.csproj index 258da09738..87d62d3fda 100644 --- a/src/Analyzers/Analyzers/test/Microsoft.AspNetCore.Analyzers.Test.csproj +++ b/src/Analyzers/Analyzers/test/Microsoft.AspNetCore.Analyzers.Test.csproj @@ -1,7 +1,7 @@ - netcoreapp3.0 + $(DefaultNetCoreTargetFramework) true Microsoft.AspNetCore.Analyzers diff --git a/src/Antiforgery/ref/Microsoft.AspNetCore.Antiforgery.csproj b/src/Antiforgery/ref/Microsoft.AspNetCore.Antiforgery.csproj index 5f34ef3e76..a2c8bd9b6b 100644 --- a/src/Antiforgery/ref/Microsoft.AspNetCore.Antiforgery.csproj +++ b/src/Antiforgery/ref/Microsoft.AspNetCore.Antiforgery.csproj @@ -1,10 +1,10 @@ - netcoreapp3.0 + netcoreapp5.0 - - + + diff --git a/src/Antiforgery/ref/Microsoft.AspNetCore.Antiforgery.netcoreapp3.0.cs b/src/Antiforgery/ref/Microsoft.AspNetCore.Antiforgery.netcoreapp.cs similarity index 100% rename from src/Antiforgery/ref/Microsoft.AspNetCore.Antiforgery.netcoreapp3.0.cs rename to src/Antiforgery/ref/Microsoft.AspNetCore.Antiforgery.netcoreapp.cs diff --git a/src/Antiforgery/src/Microsoft.AspNetCore.Antiforgery.csproj b/src/Antiforgery/src/Microsoft.AspNetCore.Antiforgery.csproj index d416ab8383..9d823f2f46 100644 --- a/src/Antiforgery/src/Microsoft.AspNetCore.Antiforgery.csproj +++ b/src/Antiforgery/src/Microsoft.AspNetCore.Antiforgery.csproj @@ -2,7 +2,7 @@ An antiforgery system for ASP.NET Core designed to generate and validate tokens to prevent Cross-Site Request Forgery attacks. - netcoreapp3.0 + $(DefaultNetCoreTargetFramework) true true aspnetcore;antiforgery diff --git a/src/Antiforgery/test/Microsoft.AspNetCore.Antiforgery.Test.csproj b/src/Antiforgery/test/Microsoft.AspNetCore.Antiforgery.Test.csproj index ff11534abc..4b4a43b6c1 100644 --- a/src/Antiforgery/test/Microsoft.AspNetCore.Antiforgery.Test.csproj +++ b/src/Antiforgery/test/Microsoft.AspNetCore.Antiforgery.Test.csproj @@ -1,7 +1,7 @@ - netcoreapp3.0 + $(DefaultNetCoreTargetFramework) diff --git a/src/Azure/AzureAD/Authentication.AzureAD.UI/ref/Microsoft.AspNetCore.Authentication.AzureAD.UI.csproj b/src/Azure/AzureAD/Authentication.AzureAD.UI/ref/Microsoft.AspNetCore.Authentication.AzureAD.UI.csproj index f320adac86..cd048a721c 100644 --- a/src/Azure/AzureAD/Authentication.AzureAD.UI/ref/Microsoft.AspNetCore.Authentication.AzureAD.UI.csproj +++ b/src/Azure/AzureAD/Authentication.AzureAD.UI/ref/Microsoft.AspNetCore.Authentication.AzureAD.UI.csproj @@ -1,10 +1,10 @@ - netcoreapp3.0 + netcoreapp5.0 - - + + diff --git a/src/Azure/AzureAD/Authentication.AzureAD.UI/ref/Microsoft.AspNetCore.Authentication.AzureAD.UI.netcoreapp3.0.cs b/src/Azure/AzureAD/Authentication.AzureAD.UI/ref/Microsoft.AspNetCore.Authentication.AzureAD.UI.netcoreapp.cs similarity index 100% rename from src/Azure/AzureAD/Authentication.AzureAD.UI/ref/Microsoft.AspNetCore.Authentication.AzureAD.UI.netcoreapp3.0.cs rename to src/Azure/AzureAD/Authentication.AzureAD.UI/ref/Microsoft.AspNetCore.Authentication.AzureAD.UI.netcoreapp.cs diff --git a/src/Azure/AzureAD/Authentication.AzureAD.UI/src/Microsoft.AspNetCore.Authentication.AzureAD.UI.csproj b/src/Azure/AzureAD/Authentication.AzureAD.UI/src/Microsoft.AspNetCore.Authentication.AzureAD.UI.csproj index baf861cf3e..6720f825e6 100644 --- a/src/Azure/AzureAD/Authentication.AzureAD.UI/src/Microsoft.AspNetCore.Authentication.AzureAD.UI.csproj +++ b/src/Azure/AzureAD/Authentication.AzureAD.UI/src/Microsoft.AspNetCore.Authentication.AzureAD.UI.csproj @@ -3,7 +3,7 @@ ASP.NET Core Azure Active Directory Integration provides components for easily integrating Azure Active Directory authentication within your ASP.NET Core application. Precompiled views assembly for the ASP.NET Core Azure Active Directory Integration package. - netcoreapp3.0 + $(DefaultNetCoreTargetFramework) aspnetcore;authentication;AzureAD true true diff --git a/src/Azure/AzureAD/Authentication.AzureAD.UI/test/Microsoft.AspNetCore.Authentication.AzureAD.UI.Test.csproj b/src/Azure/AzureAD/Authentication.AzureAD.UI/test/Microsoft.AspNetCore.Authentication.AzureAD.UI.Test.csproj index 509d46fde9..1087f46745 100644 --- a/src/Azure/AzureAD/Authentication.AzureAD.UI/test/Microsoft.AspNetCore.Authentication.AzureAD.UI.Test.csproj +++ b/src/Azure/AzureAD/Authentication.AzureAD.UI/test/Microsoft.AspNetCore.Authentication.AzureAD.UI.Test.csproj @@ -1,7 +1,7 @@ - netcoreapp3.0 + $(DefaultNetCoreTargetFramework) Microsoft.AspNetCore.Authentication.AzureAD.UI diff --git a/src/Azure/AzureAD/Authentication.AzureADB2C.UI/ref/Microsoft.AspNetCore.Authentication.AzureADB2C.UI.csproj b/src/Azure/AzureAD/Authentication.AzureADB2C.UI/ref/Microsoft.AspNetCore.Authentication.AzureADB2C.UI.csproj index 8354ceaa59..accd78a681 100644 --- a/src/Azure/AzureAD/Authentication.AzureADB2C.UI/ref/Microsoft.AspNetCore.Authentication.AzureADB2C.UI.csproj +++ b/src/Azure/AzureAD/Authentication.AzureADB2C.UI/ref/Microsoft.AspNetCore.Authentication.AzureADB2C.UI.csproj @@ -1,10 +1,10 @@ - netcoreapp3.0 + netcoreapp5.0 - - + + diff --git a/src/Azure/AzureAD/Authentication.AzureADB2C.UI/ref/Microsoft.AspNetCore.Authentication.AzureADB2C.UI.netcoreapp3.0.cs b/src/Azure/AzureAD/Authentication.AzureADB2C.UI/ref/Microsoft.AspNetCore.Authentication.AzureADB2C.UI.netcoreapp.cs similarity index 100% rename from src/Azure/AzureAD/Authentication.AzureADB2C.UI/ref/Microsoft.AspNetCore.Authentication.AzureADB2C.UI.netcoreapp3.0.cs rename to src/Azure/AzureAD/Authentication.AzureADB2C.UI/ref/Microsoft.AspNetCore.Authentication.AzureADB2C.UI.netcoreapp.cs diff --git a/src/Azure/AzureAD/Authentication.AzureADB2C.UI/src/Microsoft.AspNetCore.Authentication.AzureADB2C.UI.csproj b/src/Azure/AzureAD/Authentication.AzureADB2C.UI/src/Microsoft.AspNetCore.Authentication.AzureADB2C.UI.csproj index cf3601f1ec..2711ae5303 100644 --- a/src/Azure/AzureAD/Authentication.AzureADB2C.UI/src/Microsoft.AspNetCore.Authentication.AzureADB2C.UI.csproj +++ b/src/Azure/AzureAD/Authentication.AzureADB2C.UI/src/Microsoft.AspNetCore.Authentication.AzureADB2C.UI.csproj @@ -3,7 +3,7 @@ ASP.NET Core Azure Active Directory B2C Integration provides components for easily integrating Azure Active Directory B2C authentication within your ASP.NET Core application. Precompiled views assembly for the ASP.NET Core Azure Active Directory B2C Integration package. - netcoreapp3.0 + $(DefaultNetCoreTargetFramework) aspnetcore;authentication;AzureADB2C true true diff --git a/src/Azure/AzureAD/Authentication.AzureADB2C.UI/test/Microsoft.AspNetCore.Authentication.AzureADB2C.UI.Test.csproj b/src/Azure/AzureAD/Authentication.AzureADB2C.UI/test/Microsoft.AspNetCore.Authentication.AzureADB2C.UI.Test.csproj index 6abd66f574..7d0ea17f83 100644 --- a/src/Azure/AzureAD/Authentication.AzureADB2C.UI/test/Microsoft.AspNetCore.Authentication.AzureADB2C.UI.Test.csproj +++ b/src/Azure/AzureAD/Authentication.AzureADB2C.UI/test/Microsoft.AspNetCore.Authentication.AzureADB2C.UI.Test.csproj @@ -1,7 +1,7 @@ - netcoreapp3.0 + $(DefaultNetCoreTargetFramework) Microsoft.AspNetCore.Authentication.AzureADB2C.UI diff --git a/src/Azure/AzureAD/samples/AzureADB2CSample/AzureADB2CSample.csproj b/src/Azure/AzureAD/samples/AzureADB2CSample/AzureADB2CSample.csproj index e1b592ed20..379bc01e8d 100644 --- a/src/Azure/AzureAD/samples/AzureADB2CSample/AzureADB2CSample.csproj +++ b/src/Azure/AzureAD/samples/AzureADB2CSample/AzureADB2CSample.csproj @@ -1,7 +1,7 @@ - netcoreapp3.0 + $(DefaultNetCoreTargetFramework) diff --git a/src/Azure/AzureAD/samples/AzureADSample/AzureADSample.csproj b/src/Azure/AzureAD/samples/AzureADSample/AzureADSample.csproj index 062de172bf..c7b9f69948 100644 --- a/src/Azure/AzureAD/samples/AzureADSample/AzureADSample.csproj +++ b/src/Azure/AzureAD/samples/AzureADSample/AzureADSample.csproj @@ -1,7 +1,7 @@ - netcoreapp3.0 + $(DefaultNetCoreTargetFramework) diff --git a/src/Azure/AzureAD/test/FunctionalTests/Microsoft.AspNetCore.Authentication.AzureAD.FunctionalTests.csproj b/src/Azure/AzureAD/test/FunctionalTests/Microsoft.AspNetCore.Authentication.AzureAD.FunctionalTests.csproj index b13ff3e5f8..d2cb51194b 100644 --- a/src/Azure/AzureAD/test/FunctionalTests/Microsoft.AspNetCore.Authentication.AzureAD.FunctionalTests.csproj +++ b/src/Azure/AzureAD/test/FunctionalTests/Microsoft.AspNetCore.Authentication.AzureAD.FunctionalTests.csproj @@ -1,7 +1,7 @@ - netcoreapp3.0 + $(DefaultNetCoreTargetFramework) true @@ -21,7 +21,7 @@ - <_PublishFiles Include="$(ArtifactsBinDir)AzureAD.WebSite\$(Configuration)\netcoreapp3.0\AzureAD.WebSite.deps.json" /> + <_PublishFiles Include="$(ArtifactsBinDir)AzureAD.WebSite\$(Configuration)\$(DefaultNetCoreTargetFramework)\AzureAD.WebSite.deps.json" /> - netcoreapp3.0 + $(DefaultNetCoreTargetFramework) diff --git a/src/Azure/AzureAppServices.HostingStartup/ref/Microsoft.AspNetCore.AzureAppServices.HostingStartup.csproj b/src/Azure/AzureAppServices.HostingStartup/ref/Microsoft.AspNetCore.AzureAppServices.HostingStartup.csproj index 3984939846..383e81f912 100644 --- a/src/Azure/AzureAppServices.HostingStartup/ref/Microsoft.AspNetCore.AzureAppServices.HostingStartup.csproj +++ b/src/Azure/AzureAppServices.HostingStartup/ref/Microsoft.AspNetCore.AzureAppServices.HostingStartup.csproj @@ -1,10 +1,10 @@ - netcoreapp3.0 + netcoreapp5.0 - - + + diff --git a/src/Azure/AzureAppServices.HostingStartup/ref/Microsoft.AspNetCore.AzureAppServices.HostingStartup.netcoreapp3.0.cs b/src/Azure/AzureAppServices.HostingStartup/ref/Microsoft.AspNetCore.AzureAppServices.HostingStartup.netcoreapp.cs similarity index 100% rename from src/Azure/AzureAppServices.HostingStartup/ref/Microsoft.AspNetCore.AzureAppServices.HostingStartup.netcoreapp3.0.cs rename to src/Azure/AzureAppServices.HostingStartup/ref/Microsoft.AspNetCore.AzureAppServices.HostingStartup.netcoreapp.cs diff --git a/src/Azure/AzureAppServices.HostingStartup/src/Microsoft.AspNetCore.AzureAppServices.HostingStartup.csproj b/src/Azure/AzureAppServices.HostingStartup/src/Microsoft.AspNetCore.AzureAppServices.HostingStartup.csproj index 821f07eb68..8992d007b8 100644 --- a/src/Azure/AzureAppServices.HostingStartup/src/Microsoft.AspNetCore.AzureAppServices.HostingStartup.csproj +++ b/src/Azure/AzureAppServices.HostingStartup/src/Microsoft.AspNetCore.AzureAppServices.HostingStartup.csproj @@ -4,7 +4,7 @@ ASP.NET Core lightup integration with Azure AppServices. - netcoreapp3.0 + $(DefaultNetCoreTargetFramework) true aspnetcore;azure;appservices true diff --git a/src/Azure/AzureAppServicesIntegration/ref/Microsoft.AspNetCore.AzureAppServicesIntegration.csproj b/src/Azure/AzureAppServicesIntegration/ref/Microsoft.AspNetCore.AzureAppServicesIntegration.csproj index 10dbbbeb46..713aefa0fb 100644 --- a/src/Azure/AzureAppServicesIntegration/ref/Microsoft.AspNetCore.AzureAppServicesIntegration.csproj +++ b/src/Azure/AzureAppServicesIntegration/ref/Microsoft.AspNetCore.AzureAppServicesIntegration.csproj @@ -1,10 +1,10 @@ - netcoreapp3.0 + netcoreapp5.0 - - + + diff --git a/src/Azure/AzureAppServicesIntegration/ref/Microsoft.AspNetCore.AzureAppServicesIntegration.netcoreapp3.0.cs b/src/Azure/AzureAppServicesIntegration/ref/Microsoft.AspNetCore.AzureAppServicesIntegration.netcoreapp.cs similarity index 100% rename from src/Azure/AzureAppServicesIntegration/ref/Microsoft.AspNetCore.AzureAppServicesIntegration.netcoreapp3.0.cs rename to src/Azure/AzureAppServicesIntegration/ref/Microsoft.AspNetCore.AzureAppServicesIntegration.netcoreapp.cs diff --git a/src/Azure/AzureAppServicesIntegration/src/Microsoft.AspNetCore.AzureAppServicesIntegration.csproj b/src/Azure/AzureAppServicesIntegration/src/Microsoft.AspNetCore.AzureAppServicesIntegration.csproj index 7cd3dc1453..972ea62ff8 100644 --- a/src/Azure/AzureAppServicesIntegration/src/Microsoft.AspNetCore.AzureAppServicesIntegration.csproj +++ b/src/Azure/AzureAppServicesIntegration/src/Microsoft.AspNetCore.AzureAppServicesIntegration.csproj @@ -2,7 +2,7 @@ ASP.NET Core integration with Azure AppServices. - netcoreapp3.0 + $(DefaultNetCoreTargetFramework) $(NoWarn);CS1591 true true diff --git a/src/Azure/AzureAppServicesIntegration/test/Microsoft.AspNetCore.AzureAppServicesIntegration.Tests.csproj b/src/Azure/AzureAppServicesIntegration/test/Microsoft.AspNetCore.AzureAppServicesIntegration.Tests.csproj index d325471015..4307d47260 100644 --- a/src/Azure/AzureAppServicesIntegration/test/Microsoft.AspNetCore.AzureAppServicesIntegration.Tests.csproj +++ b/src/Azure/AzureAppServicesIntegration/test/Microsoft.AspNetCore.AzureAppServicesIntegration.Tests.csproj @@ -1,7 +1,7 @@  - netcoreapp3.0 + $(DefaultNetCoreTargetFramework) diff --git a/src/Azure/samples/AzureAppServicesHostingStartupSample/AzureAppServicesHostingStartupSample.csproj b/src/Azure/samples/AzureAppServicesHostingStartupSample/AzureAppServicesHostingStartupSample.csproj index 6985ea6c0c..81d6f1f4b5 100644 --- a/src/Azure/samples/AzureAppServicesHostingStartupSample/AzureAppServicesHostingStartupSample.csproj +++ b/src/Azure/samples/AzureAppServicesHostingStartupSample/AzureAppServicesHostingStartupSample.csproj @@ -1,7 +1,7 @@ - netcoreapp3.0 + $(DefaultNetCoreTargetFramework) diff --git a/src/Azure/samples/AzureAppServicesSample/AzureAppServicesSample.csproj b/src/Azure/samples/AzureAppServicesSample/AzureAppServicesSample.csproj index 9f7be59933..2e75ddaed4 100644 --- a/src/Azure/samples/AzureAppServicesSample/AzureAppServicesSample.csproj +++ b/src/Azure/samples/AzureAppServicesSample/AzureAppServicesSample.csproj @@ -1,7 +1,7 @@  - netcoreapp3.0 + $(DefaultNetCoreTargetFramework) diff --git a/src/Components/Analyzers/test/Microsoft.AspNetCore.Components.Analyzers.Tests.csproj b/src/Components/Analyzers/test/Microsoft.AspNetCore.Components.Analyzers.Tests.csproj index 45b379c65c..10085017d1 100644 --- a/src/Components/Analyzers/test/Microsoft.AspNetCore.Components.Analyzers.Tests.csproj +++ b/src/Components/Analyzers/test/Microsoft.AspNetCore.Components.Analyzers.Tests.csproj @@ -1,7 +1,7 @@  - netcoreapp3.0 + $(DefaultNetCoreTargetFramework) diff --git a/src/Components/Authorization/ref/Microsoft.AspNetCore.Components.Authorization.csproj b/src/Components/Authorization/ref/Microsoft.AspNetCore.Components.Authorization.csproj index d60eb31594..02d6621e2f 100644 --- a/src/Components/Authorization/ref/Microsoft.AspNetCore.Components.Authorization.csproj +++ b/src/Components/Authorization/ref/Microsoft.AspNetCore.Components.Authorization.csproj @@ -1,15 +1,15 @@ - netstandard2.0;netcoreapp3.0 + netstandard2.0;netcoreapp5.0 - - + + diff --git a/src/Components/Authorization/ref/Microsoft.AspNetCore.Components.Authorization.netcoreapp3.0.cs b/src/Components/Authorization/ref/Microsoft.AspNetCore.Components.Authorization.netcoreapp.cs similarity index 99% rename from src/Components/Authorization/ref/Microsoft.AspNetCore.Components.Authorization.netcoreapp3.0.cs rename to src/Components/Authorization/ref/Microsoft.AspNetCore.Components.Authorization.netcoreapp.cs index a19e7c1ba3..ca0535937a 100644 --- a/src/Components/Authorization/ref/Microsoft.AspNetCore.Components.Authorization.netcoreapp3.0.cs +++ b/src/Components/Authorization/ref/Microsoft.AspNetCore.Components.Authorization.netcoreapp.cs @@ -57,7 +57,7 @@ namespace Microsoft.AspNetCore.Components.Authorization public CascadingAuthenticationState() { } [Microsoft.AspNetCore.Components.ParameterAttribute] public Microsoft.AspNetCore.Components.RenderFragment ChildContent { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } - protected override void BuildRenderTree(Microsoft.AspNetCore.Components.Rendering.RenderTreeBuilder builder) { } + protected override void BuildRenderTree(Microsoft.AspNetCore.Components.Rendering.RenderTreeBuilder __builder) { } protected override void OnInitialized() { } void System.IDisposable.Dispose() { } } diff --git a/src/Components/Authorization/ref/Microsoft.AspNetCore.Components.Authorization.netstandard2.0.cs b/src/Components/Authorization/ref/Microsoft.AspNetCore.Components.Authorization.netstandard2.0.cs index a19e7c1ba3..ca0535937a 100644 --- a/src/Components/Authorization/ref/Microsoft.AspNetCore.Components.Authorization.netstandard2.0.cs +++ b/src/Components/Authorization/ref/Microsoft.AspNetCore.Components.Authorization.netstandard2.0.cs @@ -57,7 +57,7 @@ namespace Microsoft.AspNetCore.Components.Authorization public CascadingAuthenticationState() { } [Microsoft.AspNetCore.Components.ParameterAttribute] public Microsoft.AspNetCore.Components.RenderFragment ChildContent { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } - protected override void BuildRenderTree(Microsoft.AspNetCore.Components.Rendering.RenderTreeBuilder builder) { } + protected override void BuildRenderTree(Microsoft.AspNetCore.Components.Rendering.RenderTreeBuilder __builder) { } protected override void OnInitialized() { } void System.IDisposable.Dispose() { } } diff --git a/src/Components/Authorization/src/Microsoft.AspNetCore.Components.Authorization.csproj b/src/Components/Authorization/src/Microsoft.AspNetCore.Components.Authorization.csproj index 84da2d5ea2..8f3142849a 100644 --- a/src/Components/Authorization/src/Microsoft.AspNetCore.Components.Authorization.csproj +++ b/src/Components/Authorization/src/Microsoft.AspNetCore.Components.Authorization.csproj @@ -1,8 +1,8 @@ - netstandard2.0;netcoreapp3.0 - netcoreapp3.0 + netstandard2.0;$(DefaultNetCoreTargetFramework) + $(DefaultNetCoreTargetFramework) true Authentication and authorization support for Blazor applications. true diff --git a/src/Components/Authorization/test/Microsoft.AspNetCore.Components.Authorization.Tests.csproj b/src/Components/Authorization/test/Microsoft.AspNetCore.Components.Authorization.Tests.csproj index fedc3e7a4e..5f7ae8f772 100644 --- a/src/Components/Authorization/test/Microsoft.AspNetCore.Components.Authorization.Tests.csproj +++ b/src/Components/Authorization/test/Microsoft.AspNetCore.Components.Authorization.Tests.csproj @@ -1,7 +1,7 @@  - netcoreapp3.0 + $(DefaultNetCoreTargetFramework) Microsoft.AspNetCore.Components.Authorization diff --git a/src/Components/Blazor/Blazor/test/Microsoft.AspNetCore.Blazor.Tests.csproj b/src/Components/Blazor/Blazor/test/Microsoft.AspNetCore.Blazor.Tests.csproj index 43c8df3786..a0acf4173e 100644 --- a/src/Components/Blazor/Blazor/test/Microsoft.AspNetCore.Blazor.Tests.csproj +++ b/src/Components/Blazor/Blazor/test/Microsoft.AspNetCore.Blazor.Tests.csproj @@ -1,7 +1,7 @@  - netcoreapp3.0 + $(DefaultNetCoreTargetFramework) diff --git a/src/Components/Blazor/Build/src/Microsoft.AspNetCore.Blazor.Build.csproj b/src/Components/Blazor/Build/src/Microsoft.AspNetCore.Blazor.Build.csproj index adfa71ef6b..35853a4b9e 100644 --- a/src/Components/Blazor/Build/src/Microsoft.AspNetCore.Blazor.Build.csproj +++ b/src/Components/Blazor/Build/src/Microsoft.AspNetCore.Blazor.Build.csproj @@ -1,7 +1,7 @@ - netcoreapp3.0 + $(DefaultNetCoreTargetFramework) Build mechanism for ASP.NET Core Blazor applications. Exe true diff --git a/src/Components/Blazor/Build/src/ReferenceFromSource.props b/src/Components/Blazor/Build/src/ReferenceFromSource.props index 927ec8c757..8067cdc131 100644 --- a/src/Components/Blazor/Build/src/ReferenceFromSource.props +++ b/src/Components/Blazor/Build/src/ReferenceFromSource.props @@ -27,7 +27,7 @@ --> dotnet - <_BlazorCliLocation>$(MSBuildThisFileDirectory)../../DevServer/src/bin/$(Configuration)/netcoreapp3.0/blazor-devserver.dll + <_BlazorCliLocation>$(MSBuildThisFileDirectory)../../DevServer/src/bin/$(Configuration)/$(DefaultNetCoreTargetFramework)/blazor-devserver.dll exec "$(_BlazorCliLocation)" serve --applicationpath "$(MSBuildProjectDirectory)/$(OutputPath)$(TargetFileName)" $(AdditionalRunArguments) diff --git a/src/Components/Blazor/Build/test/GenericComponentRazorIntegrationTest.cs b/src/Components/Blazor/Build/test/GenericComponentRazorIntegrationTest.cs index 299683b68f..7527e83535 100644 --- a/src/Components/Blazor/Build/test/GenericComponentRazorIntegrationTest.cs +++ b/src/Components/Blazor/Build/test/GenericComponentRazorIntegrationTest.cs @@ -120,7 +120,7 @@ namespace Test AdditionalSyntaxTrees.Add(GenericContextComponent); var component = CompileToComponent(@" -() { 1, 2, })"" @ref=""_my"" @ref:suppressField /> +() { 1, 2, })"" @ref=""_my"" /> @code { GenericContext _my; @@ -187,7 +187,7 @@ namespace Test AdditionalSyntaxTrees.Add(GenericContextComponent); var component = CompileToComponent(@" -() { 1, 2, })"" @ref=""_my"" @ref:suppressField /> +() { 1, 2, })"" @ref=""_my"" /> @code { GenericContext _my; @@ -220,7 +220,7 @@ namespace Test var assembly = CompileToAssembly("Test.cshtml", @" @typeparam TItem - + @code { [Parameter] public List MyItems { get; set; } diff --git a/src/Components/Blazor/Build/test/Microsoft.AspNetCore.Blazor.Build.Tests.csproj b/src/Components/Blazor/Build/test/Microsoft.AspNetCore.Blazor.Build.Tests.csproj index 0263c9f800..02a15015e4 100644 --- a/src/Components/Blazor/Build/test/Microsoft.AspNetCore.Blazor.Build.Tests.csproj +++ b/src/Components/Blazor/Build/test/Microsoft.AspNetCore.Blazor.Build.Tests.csproj @@ -1,7 +1,7 @@  - netcoreapp3.0 + $(DefaultNetCoreTargetFramework) $(DefaultItemExcludes);TestFiles\**\* diff --git a/src/Components/Blazor/DevServer/src/Microsoft.AspNetCore.Blazor.DevServer.csproj b/src/Components/Blazor/DevServer/src/Microsoft.AspNetCore.Blazor.DevServer.csproj index b12f55ef82..d18ab7c9e4 100644 --- a/src/Components/Blazor/DevServer/src/Microsoft.AspNetCore.Blazor.DevServer.csproj +++ b/src/Components/Blazor/DevServer/src/Microsoft.AspNetCore.Blazor.DevServer.csproj @@ -1,7 +1,7 @@  - netcoreapp3.0 + $(DefaultNetCoreTargetFramework) Exe blazor-devserver Microsoft.AspNetCore.Blazor.DevServer diff --git a/src/Components/Blazor/Http/test/Microsoft.AspNetCore.Blazor.HttpClient.Tests.csproj b/src/Components/Blazor/Http/test/Microsoft.AspNetCore.Blazor.HttpClient.Tests.csproj index 375cc47dde..e7a9870ecd 100644 --- a/src/Components/Blazor/Http/test/Microsoft.AspNetCore.Blazor.HttpClient.Tests.csproj +++ b/src/Components/Blazor/Http/test/Microsoft.AspNetCore.Blazor.HttpClient.Tests.csproj @@ -1,7 +1,7 @@  - netcoreapp3.0 + $(DefaultNetCoreTargetFramework) diff --git a/src/Components/Blazor/Server/ref/Microsoft.AspNetCore.Blazor.Server.csproj b/src/Components/Blazor/Server/ref/Microsoft.AspNetCore.Blazor.Server.csproj index b72d6c201c..440c9de42e 100644 --- a/src/Components/Blazor/Server/ref/Microsoft.AspNetCore.Blazor.Server.csproj +++ b/src/Components/Blazor/Server/ref/Microsoft.AspNetCore.Blazor.Server.csproj @@ -1,10 +1,10 @@ - netcoreapp3.0 + netcoreapp5.0 - - + + diff --git a/src/Components/Blazor/Server/ref/Microsoft.AspNetCore.Blazor.Server.netcoreapp3.0.cs b/src/Components/Blazor/Server/ref/Microsoft.AspNetCore.Blazor.Server.netcoreapp.cs similarity index 100% rename from src/Components/Blazor/Server/ref/Microsoft.AspNetCore.Blazor.Server.netcoreapp3.0.cs rename to src/Components/Blazor/Server/ref/Microsoft.AspNetCore.Blazor.Server.netcoreapp.cs diff --git a/src/Components/Blazor/Server/src/Microsoft.AspNetCore.Blazor.Server.csproj b/src/Components/Blazor/Server/src/Microsoft.AspNetCore.Blazor.Server.csproj index 7770f6bdd2..2d7b2d8fb4 100644 --- a/src/Components/Blazor/Server/src/Microsoft.AspNetCore.Blazor.Server.csproj +++ b/src/Components/Blazor/Server/src/Microsoft.AspNetCore.Blazor.Server.csproj @@ -1,7 +1,7 @@  - netcoreapp3.0 + $(DefaultNetCoreTargetFramework) Runtime server features for ASP.NET Core Blazor applications. true diff --git a/src/Components/Blazor/Templates/src/content/BlazorWasm-CSharp/.template.config.src/template.json b/src/Components/Blazor/Templates/src/content/BlazorWasm-CSharp/.template.config.src/template.json index 755c0a8ad7..119303324e 100644 --- a/src/Components/Blazor/Templates/src/content/BlazorWasm-CSharp/.template.config.src/template.json +++ b/src/Components/Blazor/Templates/src/content/BlazorWasm-CSharp/.template.config.src/template.json @@ -83,12 +83,12 @@ "datatype": "choice", "choices": [ { - "choice": "netcoreapp3.0", - "description": "Target netcoreapp3.0" + "choice": "netcoreapp5.0", + "description": "Target netcoreapp5.0" } ], - "replaces": "netcoreapp3.0", - "defaultValue": "netcoreapp3.0" + "replaces": "netcoreapp5.0", + "defaultValue": "netcoreapp5.0" }, "HostIdentifier": { "type": "bind", diff --git a/src/Components/Blazor/Templates/src/content/BlazorWasm-CSharp/Server/BlazorWasm-CSharp.Server.csproj b/src/Components/Blazor/Templates/src/content/BlazorWasm-CSharp/Server/BlazorWasm-CSharp.Server.csproj index b116a53964..0d42f8eef5 100644 --- a/src/Components/Blazor/Templates/src/content/BlazorWasm-CSharp/Server/BlazorWasm-CSharp.Server.csproj +++ b/src/Components/Blazor/Templates/src/content/BlazorWasm-CSharp/Server/BlazorWasm-CSharp.Server.csproj @@ -1,7 +1,7 @@  - netcoreapp3.0 + $(DefaultNetCoreTargetFramework) 7.3 diff --git a/src/Components/Blazor/testassets/HostedInAspNet.Server/HostedInAspNet.Server.csproj b/src/Components/Blazor/testassets/HostedInAspNet.Server/HostedInAspNet.Server.csproj index 6927153f36..10605ed6fe 100644 --- a/src/Components/Blazor/testassets/HostedInAspNet.Server/HostedInAspNet.Server.csproj +++ b/src/Components/Blazor/testassets/HostedInAspNet.Server/HostedInAspNet.Server.csproj @@ -1,7 +1,7 @@  - netcoreapp3.0 + $(DefaultNetCoreTargetFramework) diff --git a/src/Components/Blazor/testassets/MonoSanity/MonoSanity.csproj b/src/Components/Blazor/testassets/MonoSanity/MonoSanity.csproj index 3afb1cf708..1373ade74e 100644 --- a/src/Components/Blazor/testassets/MonoSanity/MonoSanity.csproj +++ b/src/Components/Blazor/testassets/MonoSanity/MonoSanity.csproj @@ -1,7 +1,7 @@  - netcoreapp3.0 + $(DefaultNetCoreTargetFramework) diff --git a/src/Components/Components/perf/Microsoft.AspNetCore.Components.Performance.csproj b/src/Components/Components/perf/Microsoft.AspNetCore.Components.Performance.csproj index 8a2d0ce6d0..a893d64abd 100644 --- a/src/Components/Components/perf/Microsoft.AspNetCore.Components.Performance.csproj +++ b/src/Components/Components/perf/Microsoft.AspNetCore.Components.Performance.csproj @@ -1,7 +1,7 @@  - netcoreapp3.0 + $(DefaultNetCoreTargetFramework) Exe true true diff --git a/src/Components/Components/ref/Microsoft.AspNetCore.Components.csproj b/src/Components/Components/ref/Microsoft.AspNetCore.Components.csproj index 73094b8c81..8ad83f57d1 100644 --- a/src/Components/Components/ref/Microsoft.AspNetCore.Components.csproj +++ b/src/Components/Components/ref/Microsoft.AspNetCore.Components.csproj @@ -1,7 +1,7 @@ - netstandard2.0;netcoreapp3.0 + netstandard2.0;netcoreapp5.0 @@ -9,8 +9,8 @@ - - + + diff --git a/src/Components/Components/ref/Microsoft.AspNetCore.Components.netcoreapp3.0.cs b/src/Components/Components/ref/Microsoft.AspNetCore.Components.netcoreapp.cs similarity index 100% rename from src/Components/Components/ref/Microsoft.AspNetCore.Components.netcoreapp3.0.cs rename to src/Components/Components/ref/Microsoft.AspNetCore.Components.netcoreapp.cs diff --git a/src/Components/Components/src/Microsoft.AspNetCore.Components.csproj b/src/Components/Components/src/Microsoft.AspNetCore.Components.csproj index a889b0f4db..837116e584 100644 --- a/src/Components/Components/src/Microsoft.AspNetCore.Components.csproj +++ b/src/Components/Components/src/Microsoft.AspNetCore.Components.csproj @@ -1,8 +1,8 @@  - netstandard2.0;netcoreapp3.0 - netcoreapp3.0 + netstandard2.0;$(DefaultNetCoreTargetFramework) + $(DefaultNetCoreTargetFramework) Components feature for ASP.NET Core. true true @@ -35,7 +35,7 @@ Microsoft.AspNetCore.Components.multitarget.nuspec - Microsoft.AspNetCore.Components.netcoreapp3.0.nuspec + Microsoft.AspNetCore.Components.$(DefaultNetCoreTargetFramework).nuspec $(GenerateNuspecDependsOn);_GetNuspecDependencyPackageVersions diff --git a/src/Components/Components/src/Microsoft.AspNetCore.Components.multitarget.nuspec b/src/Components/Components/src/Microsoft.AspNetCore.Components.multitarget.nuspec index 6324ea3136..19ee259e88 100644 --- a/src/Components/Components/src/Microsoft.AspNetCore.Components.multitarget.nuspec +++ b/src/Components/Components/src/Microsoft.AspNetCore.Components.multitarget.nuspec @@ -9,7 +9,7 @@ - + diff --git a/src/Components/Components/src/Microsoft.AspNetCore.Components.netcoreapp3.0.nuspec b/src/Components/Components/src/Microsoft.AspNetCore.Components.netcoreapp5.0.nuspec similarity index 94% rename from src/Components/Components/src/Microsoft.AspNetCore.Components.netcoreapp3.0.nuspec rename to src/Components/Components/src/Microsoft.AspNetCore.Components.netcoreapp5.0.nuspec index 8dec24f270..d7bd9c31de 100644 --- a/src/Components/Components/src/Microsoft.AspNetCore.Components.netcoreapp3.0.nuspec +++ b/src/Components/Components/src/Microsoft.AspNetCore.Components.netcoreapp5.0.nuspec @@ -3,7 +3,7 @@ $CommonMetadataElements$ - + diff --git a/src/Components/Components/test/Microsoft.AspNetCore.Components.Tests.csproj b/src/Components/Components/test/Microsoft.AspNetCore.Components.Tests.csproj index e97f75fd2a..fd2a5406c1 100644 --- a/src/Components/Components/test/Microsoft.AspNetCore.Components.Tests.csproj +++ b/src/Components/Components/test/Microsoft.AspNetCore.Components.Tests.csproj @@ -1,7 +1,7 @@ - netcoreapp3.0 + $(DefaultNetCoreTargetFramework) Microsoft.AspNetCore.Components diff --git a/src/Components/Directory.Build.props b/src/Components/Directory.Build.props index ee0ff057fa..02d423b43e 100644 --- a/src/Components/Directory.Build.props +++ b/src/Components/Directory.Build.props @@ -15,7 +15,7 @@ $(MSBuildThisFileDirectory)Shared\ - $(MSBuildThisFileDirectory)Blazor\Build\src\bin\$(Configuration)\netcoreapp3.0\ + $(MSBuildThisFileDirectory)Blazor\Build\src\bin\$(Configuration)\$(DefaultNetCoreTargetFramework)\ diff --git a/src/Components/Forms/ref/Microsoft.AspNetCore.Components.Forms.csproj b/src/Components/Forms/ref/Microsoft.AspNetCore.Components.Forms.csproj index 8e24904b63..f302e6311d 100644 --- a/src/Components/Forms/ref/Microsoft.AspNetCore.Components.Forms.csproj +++ b/src/Components/Forms/ref/Microsoft.AspNetCore.Components.Forms.csproj @@ -1,15 +1,15 @@ - netstandard2.0;netcoreapp3.0 + netstandard2.0;netcoreapp5.0 - - + + diff --git a/src/Components/Forms/ref/Microsoft.AspNetCore.Components.Forms.netcoreapp3.0.cs b/src/Components/Forms/ref/Microsoft.AspNetCore.Components.Forms.netcoreapp.cs similarity index 100% rename from src/Components/Forms/ref/Microsoft.AspNetCore.Components.Forms.netcoreapp3.0.cs rename to src/Components/Forms/ref/Microsoft.AspNetCore.Components.Forms.netcoreapp.cs diff --git a/src/Components/Forms/src/Microsoft.AspNetCore.Components.Forms.csproj b/src/Components/Forms/src/Microsoft.AspNetCore.Components.Forms.csproj index dba25a048e..d338f21000 100644 --- a/src/Components/Forms/src/Microsoft.AspNetCore.Components.Forms.csproj +++ b/src/Components/Forms/src/Microsoft.AspNetCore.Components.Forms.csproj @@ -1,8 +1,8 @@ - netstandard2.0;netcoreapp3.0 - netcoreapp3.0 + netstandard2.0;$(DefaultNetCoreTargetFramework) + $(DefaultNetCoreTargetFramework) true Forms and validation support for Blazor applications. true diff --git a/src/Components/Forms/test/Microsoft.AspNetCore.Components.Forms.Tests.csproj b/src/Components/Forms/test/Microsoft.AspNetCore.Components.Forms.Tests.csproj index e78bb47ed3..6c611379a5 100644 --- a/src/Components/Forms/test/Microsoft.AspNetCore.Components.Forms.Tests.csproj +++ b/src/Components/Forms/test/Microsoft.AspNetCore.Components.Forms.Tests.csproj @@ -1,7 +1,7 @@  - netcoreapp3.0 + $(DefaultNetCoreTargetFramework) Microsoft.AspNetCore.Components.Forms diff --git a/src/Components/Server/ref/Microsoft.AspNetCore.Components.Server.csproj b/src/Components/Server/ref/Microsoft.AspNetCore.Components.Server.csproj index 27fcbed2d7..f65a212737 100644 --- a/src/Components/Server/ref/Microsoft.AspNetCore.Components.Server.csproj +++ b/src/Components/Server/ref/Microsoft.AspNetCore.Components.Server.csproj @@ -1,10 +1,10 @@ - netcoreapp3.0 + netcoreapp5.0 - - + + diff --git a/src/Components/Server/ref/Microsoft.AspNetCore.Components.Server.netcoreapp3.0.cs b/src/Components/Server/ref/Microsoft.AspNetCore.Components.Server.netcoreapp.cs similarity index 100% rename from src/Components/Server/ref/Microsoft.AspNetCore.Components.Server.netcoreapp3.0.cs rename to src/Components/Server/ref/Microsoft.AspNetCore.Components.Server.netcoreapp.cs diff --git a/src/Components/Server/src/Microsoft.AspNetCore.Components.Server.csproj b/src/Components/Server/src/Microsoft.AspNetCore.Components.Server.csproj index 84a0695bdf..234ad4bf45 100644 --- a/src/Components/Server/src/Microsoft.AspNetCore.Components.Server.csproj +++ b/src/Components/Server/src/Microsoft.AspNetCore.Components.Server.csproj @@ -1,7 +1,7 @@ - netcoreapp3.0 + $(DefaultNetCoreTargetFramework) Runtime server features for ASP.NET Core Components. true true diff --git a/src/Components/Server/test/Microsoft.AspNetCore.Components.Server.Tests.csproj b/src/Components/Server/test/Microsoft.AspNetCore.Components.Server.Tests.csproj index c80f6fc2fb..9901e6f809 100644 --- a/src/Components/Server/test/Microsoft.AspNetCore.Components.Server.Tests.csproj +++ b/src/Components/Server/test/Microsoft.AspNetCore.Components.Server.Tests.csproj @@ -1,7 +1,7 @@ - netcoreapp3.0 + $(DefaultNetCoreTargetFramework) diff --git a/src/Components/Web.JS/dist/Release/blazor.server.js b/src/Components/Web.JS/dist/Release/blazor.server.js index 31a3a61398..3d73bad3e0 100644 --- a/src/Components/Web.JS/dist/Release/blazor.server.js +++ b/src/Components/Web.JS/dist/Release/blazor.server.js @@ -12,4 +12,4 @@ var r=n(50),o=n(51),i=n(52);function a(){return c.TYPED_ARRAY_SUPPORT?2147483647 * @author Feross Aboukhadijeh * @license MIT */ -function r(e,t){if(e===t)return 0;for(var n=e.length,r=t.length,o=0,i=Math.min(n,r);o=0;u--)if(l[u]!==f[u])return!1;for(u=l.length-1;u>=0;u--)if(c=l[u],!b(e[c],t[c],n,r))return!1;return!0}(e,t,n,a))}return n?e===t:e==t}function m(e){return"[object Arguments]"==Object.prototype.toString.call(e)}function w(e,t){if(!e||!t)return!1;if("[object RegExp]"==Object.prototype.toString.call(t))return t.test(e);try{if(e instanceof t)return!0}catch(e){}return!Error.isPrototypeOf(t)&&!0===t.call({},e)}function E(e,t,n,r){var o;if("function"!=typeof t)throw new TypeError('"block" argument must be a function');"string"==typeof n&&(r=n,n=null),o=function(e){var t;try{e()}catch(e){t=e}return t}(t),r=(n&&n.name?" ("+n.name+").":".")+(r?" "+r:"."),e&&!o&&y(o,n,"Missing expected exception"+r);var a="string"==typeof r,s=!e&&o&&!n;if((!e&&i.isError(o)&&a&&w(o,n)||s)&&y(o,n,"Got unwanted exception"+r),e&&o&&n&&!w(o,n)||!e&&o)throw o}f.AssertionError=function(e){var t;this.name="AssertionError",this.actual=e.actual,this.expected=e.expected,this.operator=e.operator,e.message?(this.message=e.message,this.generatedMessage=!1):(this.message=d(g((t=this).actual),128)+" "+t.operator+" "+d(g(t.expected),128),this.generatedMessage=!0);var n=e.stackStartFunction||y;if(Error.captureStackTrace)Error.captureStackTrace(this,n);else{var r=new Error;if(r.stack){var o=r.stack,i=p(n),a=o.indexOf("\n"+i);if(a>=0){var s=o.indexOf("\n",a+1);o=o.substring(s+1)}this.stack=o}}},i.inherits(f.AssertionError,Error),f.fail=y,f.ok=v,f.equal=function(e,t,n){e!=t&&y(e,t,n,"==",f.equal)},f.notEqual=function(e,t,n){e==t&&y(e,t,n,"!=",f.notEqual)},f.deepEqual=function(e,t,n){b(e,t,!1)||y(e,t,n,"deepEqual",f.deepEqual)},f.deepStrictEqual=function(e,t,n){b(e,t,!0)||y(e,t,n,"deepStrictEqual",f.deepStrictEqual)},f.notDeepEqual=function(e,t,n){b(e,t,!1)&&y(e,t,n,"notDeepEqual",f.notDeepEqual)},f.notDeepStrictEqual=function e(t,n,r){b(t,n,!0)&&y(t,n,r,"notDeepStrictEqual",e)},f.strictEqual=function(e,t,n){e!==t&&y(e,t,n,"===",f.strictEqual)},f.notStrictEqual=function(e,t,n){e===t&&y(e,t,n,"!==",f.notStrictEqual)},f.throws=function(e,t,n){E(!0,e,t,n)},f.doesNotThrow=function(e,t,n){E(!1,e,t,n)},f.ifError=function(e){if(e)throw e};var S=Object.keys||function(e){var t=[];for(var n in e)a.call(e,n)&&t.push(n);return t}}).call(this,n(10))},function(e,t){e.exports=function(e){return e&&"object"==typeof e&&"function"==typeof e.copy&&"function"==typeof e.fill&&"function"==typeof e.readUInt8}},function(e,t){"function"==typeof Object.create?e.exports=function(e,t){e.super_=t,e.prototype=Object.create(t.prototype,{constructor:{value:e,enumerable:!1,writable:!0,configurable:!0}})}:e.exports=function(e,t){e.super_=t;var n=function(){};n.prototype=t.prototype,e.prototype=new n,e.prototype.constructor=e}},function(e,t,n){e.exports=n(11)},function(e,t){var n={}.toString;e.exports=Array.isArray||function(e){return"[object Array]"==n.call(e)}},function(e,t){},function(e,t,n){"use strict";var r=n(13).Buffer,o=n(60);e.exports=function(){function e(){!function(e,t){if(!(e instanceof t))throw new TypeError("Cannot call a class as a function")}(this,e),this.head=null,this.tail=null,this.length=0}return e.prototype.push=function(e){var t={data:e,next:null};this.length>0?this.tail.next=t:this.head=t,this.tail=t,++this.length},e.prototype.unshift=function(e){var t={data:e,next:this.head};0===this.length&&(this.tail=t),this.head=t,++this.length},e.prototype.shift=function(){if(0!==this.length){var e=this.head.data;return 1===this.length?this.head=this.tail=null:this.head=this.head.next,--this.length,e}},e.prototype.clear=function(){this.head=this.tail=null,this.length=0},e.prototype.join=function(e){if(0===this.length)return"";for(var t=this.head,n=""+t.data;t=t.next;)n+=e+t.data;return n},e.prototype.concat=function(e){if(0===this.length)return r.alloc(0);if(1===this.length)return this.head.data;for(var t,n,o,i=r.allocUnsafe(e>>>0),a=this.head,s=0;a;)t=a.data,n=i,o=s,t.copy(n,o),s+=a.data.length,a=a.next;return i},e}(),o&&o.inspect&&o.inspect.custom&&(e.exports.prototype[o.inspect.custom]=function(){var e=o.inspect({length:this.length});return this.constructor.name+" "+e})},function(e,t){},function(e,t,n){var r=n(5),o=r.Buffer;function i(e,t){for(var n in e)t[n]=e[n]}function a(e,t,n){return o(e,t,n)}o.from&&o.alloc&&o.allocUnsafe&&o.allocUnsafeSlow?e.exports=r:(i(r,t),t.Buffer=a),i(o,a),a.from=function(e,t,n){if("number"==typeof e)throw new TypeError("Argument must not be a number");return o(e,t,n)},a.alloc=function(e,t,n){if("number"!=typeof e)throw new TypeError("Argument must be a number");var r=o(e);return void 0!==t?"string"==typeof n?r.fill(t,n):r.fill(t):r.fill(0),r},a.allocUnsafe=function(e){if("number"!=typeof e)throw new TypeError("Argument must be a number");return o(e)},a.allocUnsafeSlow=function(e){if("number"!=typeof e)throw new TypeError("Argument must be a number");return r.SlowBuffer(e)}},function(e,t,n){(function(e){var r=void 0!==e&&e||"undefined"!=typeof self&&self||window,o=Function.prototype.apply;function i(e,t){this._id=e,this._clearFn=t}t.setTimeout=function(){return new i(o.call(setTimeout,r,arguments),clearTimeout)},t.setInterval=function(){return new i(o.call(setInterval,r,arguments),clearInterval)},t.clearTimeout=t.clearInterval=function(e){e&&e.close()},i.prototype.unref=i.prototype.ref=function(){},i.prototype.close=function(){this._clearFn.call(r,this._id)},t.enroll=function(e,t){clearTimeout(e._idleTimeoutId),e._idleTimeout=t},t.unenroll=function(e){clearTimeout(e._idleTimeoutId),e._idleTimeout=-1},t._unrefActive=t.active=function(e){clearTimeout(e._idleTimeoutId);var t=e._idleTimeout;t>=0&&(e._idleTimeoutId=setTimeout(function(){e._onTimeout&&e._onTimeout()},t))},n(63),t.setImmediate="undefined"!=typeof self&&self.setImmediate||void 0!==e&&e.setImmediate||this&&this.setImmediate,t.clearImmediate="undefined"!=typeof self&&self.clearImmediate||void 0!==e&&e.clearImmediate||this&&this.clearImmediate}).call(this,n(10))},function(e,t,n){(function(e,t){!function(e,n){"use strict";if(!e.setImmediate){var r,o,i,a,s,c=1,u={},l=!1,f=e.document,h=Object.getPrototypeOf&&Object.getPrototypeOf(e);h=h&&h.setTimeout?h:e,"[object process]"==={}.toString.call(e.process)?r=function(e){t.nextTick(function(){d(e)})}:!function(){if(e.postMessage&&!e.importScripts){var t=!0,n=e.onmessage;return e.onmessage=function(){t=!1},e.postMessage("","*"),e.onmessage=n,t}}()?e.MessageChannel?((i=new MessageChannel).port1.onmessage=function(e){d(e.data)},r=function(e){i.port2.postMessage(e)}):f&&"onreadystatechange"in f.createElement("script")?(o=f.documentElement,r=function(e){var t=f.createElement("script");t.onreadystatechange=function(){d(e),t.onreadystatechange=null,o.removeChild(t),t=null},o.appendChild(t)}):r=function(e){setTimeout(d,0,e)}:(a="setImmediate$"+Math.random()+"$",s=function(t){t.source===e&&"string"==typeof t.data&&0===t.data.indexOf(a)&&d(+t.data.slice(a.length))},e.addEventListener?e.addEventListener("message",s,!1):e.attachEvent("onmessage",s),r=function(t){e.postMessage(a+t,"*")}),h.setImmediate=function(e){"function"!=typeof e&&(e=new Function(""+e));for(var t=new Array(arguments.length-1),n=0;n0?this._transform(null,t,n):n()},e.exports.decoder=c,e.exports.encoder=s},function(e,t,n){(t=e.exports=n(36)).Stream=t,t.Readable=t,t.Writable=n(41),t.Duplex=n(11),t.Transform=n(42),t.PassThrough=n(67)},function(e,t,n){"use strict";e.exports=i;var r=n(42),o=n(20);function i(e){if(!(this instanceof i))return new i(e);r.call(this,e)}o.inherits=n(14),o.inherits(i,r),i.prototype._transform=function(e,t,n){n(null,e)}},function(e,t,n){var r=n(22);function o(e){Error.call(this),Error.captureStackTrace&&Error.captureStackTrace(this,this.constructor),this.name=this.constructor.name,this.message=e||"unable to decode"}n(35).inherits(o,Error),e.exports=function(e){return function(e){e instanceof r||(e=r().append(e));var t=i(e);if(t)return e.consume(t.bytesConsumed),t.value;throw new o};function t(e,t,n){return t>=n+e}function n(e,t){return{value:e,bytesConsumed:t}}function i(e,r){r=void 0===r?0:r;var o=e.length-r;if(o<=0)return null;var i,l,f,h=e.readUInt8(r),p=0;if(!function(e,t){var n=function(e){switch(e){case 196:return 2;case 197:return 3;case 198:return 5;case 199:return 3;case 200:return 4;case 201:return 6;case 202:return 5;case 203:return 9;case 204:return 2;case 205:return 3;case 206:return 5;case 207:return 9;case 208:return 2;case 209:return 3;case 210:return 5;case 211:return 9;case 212:return 3;case 213:return 4;case 214:return 6;case 215:return 10;case 216:return 18;case 217:return 2;case 218:return 3;case 219:return 5;case 222:return 3;default:return-1}}(e);return!(-1!==n&&t=0;f--)p+=e.readUInt8(r+f+1)*Math.pow(2,8*(7-f));return n(p,9);case 208:return n(p=e.readInt8(r+1),2);case 209:return n(p=e.readInt16BE(r+1),3);case 210:return n(p=e.readInt32BE(r+1),5);case 211:return n(p=function(e,t){var n=128==(128&e[t]);if(n)for(var r=1,o=t+7;o>=t;o--){var i=(255^e[o])+r;e[o]=255&i,r=i>>8}var a=e.readUInt32BE(t+0),s=e.readUInt32BE(t+4);return(4294967296*a+s)*(n?-1:1)}(e.slice(r+1,r+9),0),9);case 202:return n(p=e.readFloatBE(r+1),5);case 203:return n(p=e.readDoubleBE(r+1),9);case 217:return t(i=e.readUInt8(r+1),o,2)?n(p=e.toString("utf8",r+2,r+2+i),2+i):null;case 218:return t(i=e.readUInt16BE(r+1),o,3)?n(p=e.toString("utf8",r+3,r+3+i),3+i):null;case 219:return t(i=e.readUInt32BE(r+1),o,5)?n(p=e.toString("utf8",r+5,r+5+i),5+i):null;case 196:return t(i=e.readUInt8(r+1),o,2)?n(p=e.slice(r+2,r+2+i),2+i):null;case 197:return t(i=e.readUInt16BE(r+1),o,3)?n(p=e.slice(r+3,r+3+i),3+i):null;case 198:return t(i=e.readUInt32BE(r+1),o,5)?n(p=e.slice(r+5,r+5+i),5+i):null;case 220:return o<3?null:(i=e.readUInt16BE(r+1),a(e,r,i,3));case 221:return o<5?null:(i=e.readUInt32BE(r+1),a(e,r,i,5));case 222:return i=e.readUInt16BE(r+1),s(e,r,i,3);case 223:throw new Error("map too big to decode in JS");case 212:return c(e,r,1);case 213:return c(e,r,2);case 214:return c(e,r,4);case 215:return c(e,r,8);case 216:return c(e,r,16);case 199:return i=e.readUInt8(r+1),l=e.readUInt8(r+2),t(i,o,3)?u(e,r,l,i,3):null;case 200:return i=e.readUInt16BE(r+1),l=e.readUInt8(r+3),t(i,o,4)?u(e,r,l,i,4):null;case 201:return i=e.readUInt32BE(r+1),l=e.readUInt8(r+5),t(i,o,6)?u(e,r,l,i,6):null}if(144==(240&h))return a(e,r,i=15&h,1);if(128==(240&h))return s(e,r,i=15&h,1);if(160==(224&h))return t(i=31&h,o,1)?n(p=e.toString("utf8",r+1,r+i+1),i+1):null;if(h>=224)return n(p=h-256,1);if(h<128)return n(h,1);throw new Error("not implemented yet")}function a(e,t,r,o){var a,s=[],c=0;for(t+=o,a=0;ai)&&((n=r.allocUnsafe(9))[0]=203,n.writeDoubleBE(e,1)),n}e.exports=function(e,t,n,i){function s(c,u){var l,f,h;if(void 0===c)throw new Error("undefined is not encodable in msgpack!");if(null===c)(l=r.allocUnsafe(1))[0]=192;else if(!0===c)(l=r.allocUnsafe(1))[0]=195;else if(!1===c)(l=r.allocUnsafe(1))[0]=194;else if("string"==typeof c)(f=r.byteLength(c))<32?((l=r.allocUnsafe(1+f))[0]=160|f,f>0&&l.write(c,1)):f<=255&&!n?((l=r.allocUnsafe(2+f))[0]=217,l[1]=f,l.write(c,2)):f<=65535?((l=r.allocUnsafe(3+f))[0]=218,l.writeUInt16BE(f,1),l.write(c,3)):((l=r.allocUnsafe(5+f))[0]=219,l.writeUInt32BE(f,1),l.write(c,5));else if(c&&(c.readUInt32LE||c instanceof Uint8Array))c instanceof Uint8Array&&(c=r.from(c)),c.length<=255?((l=r.allocUnsafe(2))[0]=196,l[1]=c.length):c.length<=65535?((l=r.allocUnsafe(3))[0]=197,l.writeUInt16BE(c.length,1)):((l=r.allocUnsafe(5))[0]=198,l.writeUInt32BE(c.length,1)),l=o([l,c]);else if(Array.isArray(c))c.length<16?(l=r.allocUnsafe(1))[0]=144|c.length:c.length<65536?((l=r.allocUnsafe(3))[0]=220,l.writeUInt16BE(c.length,1)):((l=r.allocUnsafe(5))[0]=221,l.writeUInt32BE(c.length,1)),l=c.reduce(function(e,t){return e.append(s(t,!0)),e},o().append(l));else{if(!i&&"function"==typeof c.getDate)return function(e){var t,n=1*e,i=Math.floor(n/1e3),a=1e6*(n-1e3*i);if(a||i>4294967295){(t=new r(10))[0]=215,t[1]=-1;var s=4*a,c=i/Math.pow(2,32),u=s+c&4294967295,l=4294967295&i;t.writeInt32BE(u,2),t.writeInt32BE(l,6)}else(t=new r(6))[0]=214,t[1]=-1,t.writeUInt32BE(Math.floor(n/1e3),2);return o().append(t)}(c);if("object"==typeof c)l=function(t){var n,i,a=-1,s=[];for(n=0;n>8),s.push(255&a)):(s.push(201),s.push(a>>24),s.push(a>>16&255),s.push(a>>8&255),s.push(255&a));return o().append(r.from(s)).append(i)}(c)||function(e){var t,n,i=[],a=0;for(t in e)e.hasOwnProperty(t)&&void 0!==e[t]&&"function"!=typeof e[t]&&(++a,i.push(s(t,!0)),i.push(s(e[t],!0)));a<16?(n=r.allocUnsafe(1))[0]=128|a:((n=r.allocUnsafe(3))[0]=222,n.writeUInt16BE(a,1));return i.unshift(n),i.reduce(function(e,t){return e.append(t)},o())}(c);else if("number"==typeof c){if((h=c)!==Math.floor(h))return a(c,t);if(c>=0)if(c<128)(l=r.allocUnsafe(1))[0]=c;else if(c<256)(l=r.allocUnsafe(2))[0]=204,l[1]=c;else if(c<65536)(l=r.allocUnsafe(3))[0]=205,l.writeUInt16BE(c,1);else if(c<=4294967295)(l=r.allocUnsafe(5))[0]=206,l.writeUInt32BE(c,1);else{if(!(c<=9007199254740991))return a(c,!0);(l=r.allocUnsafe(9))[0]=207,function(e,t){for(var n=7;n>=0;n--)e[n+1]=255&t,t/=256}(l,c)}else if(c>=-32)(l=r.allocUnsafe(1))[0]=256+c;else if(c>=-128)(l=r.allocUnsafe(2))[0]=208,l.writeInt8(c,1);else if(c>=-32768)(l=r.allocUnsafe(3))[0]=209,l.writeInt16BE(c,1);else if(c>-214748365)(l=r.allocUnsafe(5))[0]=210,l.writeInt32BE(c,1);else{if(!(c>=-9007199254740991))return a(c,!0);(l=r.allocUnsafe(9))[0]=211,function(e,t,n){var r=n<0;r&&(n=Math.abs(n));var o=n%4294967296,i=n/4294967296;if(e.writeUInt32BE(Math.floor(i),t+0),e.writeUInt32BE(o,t+4),r)for(var a=1,s=t+7;s>=t;s--){var c=(255^e[s])+a;e[s]=255&c,a=c>>8}}(l,1,c)}}}if(!l)throw new Error("not implemented yet");return u?l:l.slice()}return s}},function(e,t,n){"use strict";var r=this&&this.__awaiter||function(e,t,n,r){return new(n||(n=Promise))(function(o,i){function a(e){try{c(r.next(e))}catch(e){i(e)}}function s(e){try{c(r.throw(e))}catch(e){i(e)}}function c(e){e.done?o(e.value):new n(function(t){t(e.value)}).then(a,s)}c((r=r.apply(e,t||[])).next())})},o=this&&this.__generator||function(e,t){var n,r,o,i,a={label:0,sent:function(){if(1&o[0])throw o[1];return o[1]},trys:[],ops:[]};return i={next:s(0),throw:s(1),return:s(2)},"function"==typeof Symbol&&(i[Symbol.iterator]=function(){return this}),i;function s(i){return function(s){return function(i){if(n)throw new TypeError("Generator is already executing.");for(;a;)try{if(n=1,r&&(o=2&i[0]?r.return:i[0]?r.throw||((o=r.return)&&o.call(r),0):r.next)&&!(o=o.call(r,i[1])).done)return o;switch(r=0,o&&(i=[2&i[0],o.value]),i[0]){case 0:case 1:o=i;break;case 4:return a.label++,{value:i[1],done:!1};case 5:a.label++,r=i[1],i=[0];continue;case 7:i=a.ops.pop(),a.trys.pop();continue;default:if(!(o=(o=a.trys).length>0&&o[o.length-1])&&(6===i[0]||2===i[0])){a=0;continue}if(3===i[0]&&(!o||i[1]>o[0]&&i[1]this.nextBatchId?this.fatalError?(this.logger.log(s.LogLevel.Debug,"Received a new batch "+e+" but errored out on a previous batch "+(this.nextBatchId-1)),[4,n.send("OnRenderCompleted",this.nextBatchId-1,this.fatalError.toString())]):[3,4]:[3,5];case 3:return o.sent(),[2];case 4:return this.logger.log(s.LogLevel.Debug,"Waiting for batch "+this.nextBatchId+". Batch "+e+" not processed."),[2];case 5:return o.trys.push([5,7,,8]),this.nextBatchId++,this.logger.log(s.LogLevel.Debug,"Applying batch "+e+"."),i.renderBatch(this.browserRendererId,new a.OutOfProcessRenderBatch(t)),[4,this.completeBatch(n,e)];case 6:return o.sent(),[3,8];case 7:throw r=o.sent(),this.fatalError=r.toString(),this.logger.log(s.LogLevel.Error,"There was an error applying batch "+e+"."),n.send("OnRenderCompleted",e,r.toString()),r;case 8:return[2]}})})},e.prototype.getLastBatchid=function(){return this.nextBatchId-1},e.prototype.completeBatch=function(e,t){return r(this,void 0,void 0,function(){return o(this,function(n){switch(n.label){case 0:return n.trys.push([0,2,,3]),[4,e.send("OnRenderCompleted",t,null)];case 1:return n.sent(),[3,3];case 2:return n.sent(),this.logger.log(s.LogLevel.Warning,"Failed to deliver completion notification for render '"+t+"'."),[3,3];case 3:return[2]}})})},e}();t.RenderQueue=c},function(e,t,n){"use strict";Object.defineProperty(t,"__esModule",{value:!0});var r=n(72),o=Math.pow(2,32),i=Math.pow(2,21)-1,a=function(){function e(e){this.batchData=e;var t=new l(e);this.arrayRangeReader=new f(e),this.arrayBuilderSegmentReader=new h(e),this.diffReader=new s(e),this.editReader=new c(e,t),this.frameReader=new u(e,t)}return e.prototype.updatedComponents=function(){return p(this.batchData,this.batchData.length-20)},e.prototype.referenceFrames=function(){return p(this.batchData,this.batchData.length-16)},e.prototype.disposedComponentIds=function(){return p(this.batchData,this.batchData.length-12)},e.prototype.disposedEventHandlerIds=function(){return p(this.batchData,this.batchData.length-8)},e.prototype.updatedComponentsEntry=function(e,t){var n=e+4*t;return p(this.batchData,n)},e.prototype.referenceFramesEntry=function(e,t){return e+20*t},e.prototype.disposedComponentIdsEntry=function(e,t){var n=e+4*t;return p(this.batchData,n)},e.prototype.disposedEventHandlerIdsEntry=function(e,t){var n=e+8*t;return g(this.batchData,n)},e}();t.OutOfProcessRenderBatch=a;var s=function(){function e(e){this.batchDataUint8=e}return e.prototype.componentId=function(e){return p(this.batchDataUint8,e)},e.prototype.edits=function(e){return e+4},e.prototype.editsEntry=function(e,t){return e+16*t},e}(),c=function(){function e(e,t){this.batchDataUint8=e,this.stringReader=t}return e.prototype.editType=function(e){return p(this.batchDataUint8,e)},e.prototype.siblingIndex=function(e){return p(this.batchDataUint8,e+4)},e.prototype.newTreeIndex=function(e){return p(this.batchDataUint8,e+8)},e.prototype.moveToSiblingIndex=function(e){return p(this.batchDataUint8,e+8)},e.prototype.removedAttributeName=function(e){var t=p(this.batchDataUint8,e+12);return this.stringReader.readString(t)},e}(),u=function(){function e(e,t){this.batchDataUint8=e,this.stringReader=t}return e.prototype.frameType=function(e){return p(this.batchDataUint8,e)},e.prototype.subtreeLength=function(e){return p(this.batchDataUint8,e+4)},e.prototype.elementReferenceCaptureId=function(e){var t=p(this.batchDataUint8,e+4);return this.stringReader.readString(t)},e.prototype.componentId=function(e){return p(this.batchDataUint8,e+8)},e.prototype.elementName=function(e){var t=p(this.batchDataUint8,e+8);return this.stringReader.readString(t)},e.prototype.textContent=function(e){var t=p(this.batchDataUint8,e+4);return this.stringReader.readString(t)},e.prototype.markupContent=function(e){var t=p(this.batchDataUint8,e+4);return this.stringReader.readString(t)},e.prototype.attributeName=function(e){var t=p(this.batchDataUint8,e+4);return this.stringReader.readString(t)},e.prototype.attributeValue=function(e){var t=p(this.batchDataUint8,e+8);return this.stringReader.readString(t)},e.prototype.attributeEventHandlerId=function(e){return g(this.batchDataUint8,e+12)},e}(),l=function(){function e(e){this.batchDataUint8=e,this.stringTableStartIndex=p(e,e.length-4)}return e.prototype.readString=function(e){if(-1===e)return null;var t,n=p(this.batchDataUint8,this.stringTableStartIndex+4*e),o=function(e,t){for(var n=0,r=0,o=0;o<4;o++){var i=e[t+o];if(n|=(127&i)<>>0)}function g(e,t){var n=d(e,t+4);if(n>i)throw new Error("Cannot read uint64 with high order part "+n+", because the result would exceed Number.MAX_SAFE_INTEGER.");return n*o+d(e,t)}},function(e,t,n){"use strict";Object.defineProperty(t,"__esModule",{value:!0});var r="function"==typeof TextDecoder?new TextDecoder("utf-8"):null;t.decodeUtf8=r?r.decode.bind(r):function(e){var t=0,n=e.length,r=[],o=[];for(;t65535&&(u-=65536,r.push(u>>>10&1023|55296),u=56320|1023&u),r.push(u)}r.length>1024&&(o.push(String.fromCharCode.apply(null,r)),r.length=0)}return o.push(String.fromCharCode.apply(null,r)),o.join("")}},function(e,t,n){"use strict";Object.defineProperty(t,"__esModule",{value:!0});var r=n(15),o=function(){function e(){}return e.prototype.log=function(e,t){},e.instance=new e,e}();t.NullLogger=o;var i=function(){function e(e){this.minimumLogLevel=e}return e.prototype.log=function(e,t){if(e>=this.minimumLogLevel)switch(e){case r.LogLevel.Critical:case r.LogLevel.Error:console.error("["+(new Date).toISOString()+"] "+r.LogLevel[e]+": "+t);break;case r.LogLevel.Warning:console.warn("["+(new Date).toISOString()+"] "+r.LogLevel[e]+": "+t);break;case r.LogLevel.Information:console.info("["+(new Date).toISOString()+"] "+r.LogLevel[e]+": "+t);break;default:console.log("["+(new Date).toISOString()+"] "+r.LogLevel[e]+": "+t)}},e}();t.ConsoleLogger=i},function(e,t,n){"use strict";var r=this&&this.__awaiter||function(e,t,n,r){return new(n||(n=Promise))(function(o,i){function a(e){try{c(r.next(e))}catch(e){i(e)}}function s(e){try{c(r.throw(e))}catch(e){i(e)}}function c(e){e.done?o(e.value):new n(function(t){t(e.value)}).then(a,s)}c((r=r.apply(e,t||[])).next())})},o=this&&this.__generator||function(e,t){var n,r,o,i,a={label:0,sent:function(){if(1&o[0])throw o[1];return o[1]},trys:[],ops:[]};return i={next:s(0),throw:s(1),return:s(2)},"function"==typeof Symbol&&(i[Symbol.iterator]=function(){return this}),i;function s(i){return function(s){return function(i){if(n)throw new TypeError("Generator is already executing.");for(;a;)try{if(n=1,r&&(o=2&i[0]?r.return:i[0]?r.throw||((o=r.return)&&o.call(r),0):r.next)&&!(o=o.call(r,i[1])).done)return o;switch(r=0,o&&(i=[2&i[0],o.value]),i[0]){case 0:case 1:o=i;break;case 4:return a.label++,{value:i[1],done:!1};case 5:a.label++,r=i[1],i=[0];continue;case 7:i=a.ops.pop(),a.trys.pop();continue;default:if(!(o=(o=a.trys).length>0&&o[o.length-1])&&(6===i[0]||2===i[0])){a=0;continue}if(3===i[0]&&(!o||i[1]>o[0]&&i[1]0&&o[o.length-1])&&(6===i[0]||2===i[0])){a=0;continue}if(3===i[0]&&(!o||i[1]>o[0]&&i[1]0&&o[o.length-1])&&(6===i[0]||2===i[0])){a=0;continue}if(3===i[0]&&(!o||i[1]>o[0]&&i[1]reloading the page if you're unable to reconnect."},e.prototype.rejected=function(){this.button.style.display="none",this.reloadParagraph.style.display="none",this.message.innerHTML="Could not reconnect to the server. Reload the page to restore functionality."},e}();t.DefaultReconnectDisplay=a},function(e,t,n){"use strict";Object.defineProperty(t,"__esModule",{value:!0});var r=function(){function e(e){this.dialog=e}return e.prototype.show=function(){this.removeClasses(),this.dialog.classList.add(e.ShowClassName)},e.prototype.hide=function(){this.removeClasses(),this.dialog.classList.add(e.HideClassName)},e.prototype.failed=function(){this.removeClasses(),this.dialog.classList.add(e.FailedClassName)},e.prototype.rejected=function(){this.removeClasses(),this.dialog.classList.add(e.RefusedClassName)},e.prototype.removeClasses=function(){this.dialog.classList.remove(e.ShowClassName,e.HideClassName,e.FailedClassName,e.RefusedClassName)},e.ShowClassName="components-reconnect-show",e.HideClassName="components-reconnect-hide",e.FailedClassName="components-reconnect-failed",e.RefusedClassName="components-reconnect-refused",e}();t.UserSpecifiedDisplay=r},function(e,t,n){"use strict";n.r(t);var r=n(5),o=n(12),i=n(2),a=function(){function e(){}return e.write=function(e){var t=e.byteLength||e.length,n=[];do{var r=127&t;(t>>=7)>0&&(r|=128),n.push(r)}while(t>0);t=e.byteLength||e.length;var o=new Uint8Array(n.length+t);return o.set(n,0),o.set(e,n.length),o.buffer},e.parse=function(e){for(var t=[],n=new Uint8Array(e),r=[0,7,14,21,28],o=0;o7)throw new Error("Messages bigger than 2GB are not supported.");if(!(n.byteLength>=o+i+a))throw new Error("Incomplete message.");t.push(n.slice?n.slice(o+i,o+i+a):n.subarray(o+i,o+i+a)),o=o+i+a}return t},e}();var s=new Uint8Array([145,i.MessageType.Ping]),c=function(){function e(){this.name="messagepack",this.version=1,this.transferFormat=i.TransferFormat.Binary,this.errorResult=1,this.voidResult=2,this.nonVoidResult=3}return e.prototype.parseMessages=function(e,t){if(!(e instanceof r.Buffer||(n=e,n&&"undefined"!=typeof ArrayBuffer&&(n instanceof ArrayBuffer||n.constructor&&"ArrayBuffer"===n.constructor.name))))throw new Error("Invalid input for MessagePack hub protocol. Expected an ArrayBuffer or Buffer.");var n;null===t&&(t=i.NullLogger.instance);for(var o=[],s=0,c=a.parse(e);s=0;u--)if(l[u]!==f[u])return!1;for(u=l.length-1;u>=0;u--)if(c=l[u],!b(e[c],t[c],n,r))return!1;return!0}(e,t,n,a))}return n?e===t:e==t}function m(e){return"[object Arguments]"==Object.prototype.toString.call(e)}function w(e,t){if(!e||!t)return!1;if("[object RegExp]"==Object.prototype.toString.call(t))return t.test(e);try{if(e instanceof t)return!0}catch(e){}return!Error.isPrototypeOf(t)&&!0===t.call({},e)}function E(e,t,n,r){var o;if("function"!=typeof t)throw new TypeError('"block" argument must be a function');"string"==typeof n&&(r=n,n=null),o=function(e){var t;try{e()}catch(e){t=e}return t}(t),r=(n&&n.name?" ("+n.name+").":".")+(r?" "+r:"."),e&&!o&&y(o,n,"Missing expected exception"+r);var a="string"==typeof r,s=!e&&o&&!n;if((!e&&i.isError(o)&&a&&w(o,n)||s)&&y(o,n,"Got unwanted exception"+r),e&&o&&n&&!w(o,n)||!e&&o)throw o}f.AssertionError=function(e){var t;this.name="AssertionError",this.actual=e.actual,this.expected=e.expected,this.operator=e.operator,e.message?(this.message=e.message,this.generatedMessage=!1):(this.message=d(g((t=this).actual),128)+" "+t.operator+" "+d(g(t.expected),128),this.generatedMessage=!0);var n=e.stackStartFunction||y;if(Error.captureStackTrace)Error.captureStackTrace(this,n);else{var r=new Error;if(r.stack){var o=r.stack,i=p(n),a=o.indexOf("\n"+i);if(a>=0){var s=o.indexOf("\n",a+1);o=o.substring(s+1)}this.stack=o}}},i.inherits(f.AssertionError,Error),f.fail=y,f.ok=v,f.equal=function(e,t,n){e!=t&&y(e,t,n,"==",f.equal)},f.notEqual=function(e,t,n){e==t&&y(e,t,n,"!=",f.notEqual)},f.deepEqual=function(e,t,n){b(e,t,!1)||y(e,t,n,"deepEqual",f.deepEqual)},f.deepStrictEqual=function(e,t,n){b(e,t,!0)||y(e,t,n,"deepStrictEqual",f.deepStrictEqual)},f.notDeepEqual=function(e,t,n){b(e,t,!1)&&y(e,t,n,"notDeepEqual",f.notDeepEqual)},f.notDeepStrictEqual=function e(t,n,r){b(t,n,!0)&&y(t,n,r,"notDeepStrictEqual",e)},f.strictEqual=function(e,t,n){e!==t&&y(e,t,n,"===",f.strictEqual)},f.notStrictEqual=function(e,t,n){e===t&&y(e,t,n,"!==",f.notStrictEqual)},f.throws=function(e,t,n){E(!0,e,t,n)},f.doesNotThrow=function(e,t,n){E(!1,e,t,n)},f.ifError=function(e){if(e)throw e};var S=Object.keys||function(e){var t=[];for(var n in e)a.call(e,n)&&t.push(n);return t}}).call(this,n(10))},function(e,t){e.exports=function(e){return e&&"object"==typeof e&&"function"==typeof e.copy&&"function"==typeof e.fill&&"function"==typeof e.readUInt8}},function(e,t){"function"==typeof Object.create?e.exports=function(e,t){e.super_=t,e.prototype=Object.create(t.prototype,{constructor:{value:e,enumerable:!1,writable:!0,configurable:!0}})}:e.exports=function(e,t){e.super_=t;var n=function(){};n.prototype=t.prototype,e.prototype=new n,e.prototype.constructor=e}},function(e,t,n){e.exports=n(11)},function(e,t){var n={}.toString;e.exports=Array.isArray||function(e){return"[object Array]"==n.call(e)}},function(e,t){},function(e,t,n){"use strict";var r=n(13).Buffer,o=n(60);e.exports=function(){function e(){!function(e,t){if(!(e instanceof t))throw new TypeError("Cannot call a class as a function")}(this,e),this.head=null,this.tail=null,this.length=0}return e.prototype.push=function(e){var t={data:e,next:null};this.length>0?this.tail.next=t:this.head=t,this.tail=t,++this.length},e.prototype.unshift=function(e){var t={data:e,next:this.head};0===this.length&&(this.tail=t),this.head=t,++this.length},e.prototype.shift=function(){if(0!==this.length){var e=this.head.data;return 1===this.length?this.head=this.tail=null:this.head=this.head.next,--this.length,e}},e.prototype.clear=function(){this.head=this.tail=null,this.length=0},e.prototype.join=function(e){if(0===this.length)return"";for(var t=this.head,n=""+t.data;t=t.next;)n+=e+t.data;return n},e.prototype.concat=function(e){if(0===this.length)return r.alloc(0);if(1===this.length)return this.head.data;for(var t,n,o,i=r.allocUnsafe(e>>>0),a=this.head,s=0;a;)t=a.data,n=i,o=s,t.copy(n,o),s+=a.data.length,a=a.next;return i},e}(),o&&o.inspect&&o.inspect.custom&&(e.exports.prototype[o.inspect.custom]=function(){var e=o.inspect({length:this.length});return this.constructor.name+" "+e})},function(e,t){},function(e,t,n){var r=n(5),o=r.Buffer;function i(e,t){for(var n in e)t[n]=e[n]}function a(e,t,n){return o(e,t,n)}o.from&&o.alloc&&o.allocUnsafe&&o.allocUnsafeSlow?e.exports=r:(i(r,t),t.Buffer=a),i(o,a),a.from=function(e,t,n){if("number"==typeof e)throw new TypeError("Argument must not be a number");return o(e,t,n)},a.alloc=function(e,t,n){if("number"!=typeof e)throw new TypeError("Argument must be a number");var r=o(e);return void 0!==t?"string"==typeof n?r.fill(t,n):r.fill(t):r.fill(0),r},a.allocUnsafe=function(e){if("number"!=typeof e)throw new TypeError("Argument must be a number");return o(e)},a.allocUnsafeSlow=function(e){if("number"!=typeof e)throw new TypeError("Argument must be a number");return r.SlowBuffer(e)}},function(e,t,n){(function(e){var r=void 0!==e&&e||"undefined"!=typeof self&&self||window,o=Function.prototype.apply;function i(e,t){this._id=e,this._clearFn=t}t.setTimeout=function(){return new i(o.call(setTimeout,r,arguments),clearTimeout)},t.setInterval=function(){return new i(o.call(setInterval,r,arguments),clearInterval)},t.clearTimeout=t.clearInterval=function(e){e&&e.close()},i.prototype.unref=i.prototype.ref=function(){},i.prototype.close=function(){this._clearFn.call(r,this._id)},t.enroll=function(e,t){clearTimeout(e._idleTimeoutId),e._idleTimeout=t},t.unenroll=function(e){clearTimeout(e._idleTimeoutId),e._idleTimeout=-1},t._unrefActive=t.active=function(e){clearTimeout(e._idleTimeoutId);var t=e._idleTimeout;t>=0&&(e._idleTimeoutId=setTimeout(function(){e._onTimeout&&e._onTimeout()},t))},n(63),t.setImmediate="undefined"!=typeof self&&self.setImmediate||void 0!==e&&e.setImmediate||this&&this.setImmediate,t.clearImmediate="undefined"!=typeof self&&self.clearImmediate||void 0!==e&&e.clearImmediate||this&&this.clearImmediate}).call(this,n(10))},function(e,t,n){(function(e,t){!function(e,n){"use strict";if(!e.setImmediate){var r,o,i,a,s,c=1,u={},l=!1,f=e.document,h=Object.getPrototypeOf&&Object.getPrototypeOf(e);h=h&&h.setTimeout?h:e,"[object process]"==={}.toString.call(e.process)?r=function(e){t.nextTick(function(){d(e)})}:!function(){if(e.postMessage&&!e.importScripts){var t=!0,n=e.onmessage;return e.onmessage=function(){t=!1},e.postMessage("","*"),e.onmessage=n,t}}()?e.MessageChannel?((i=new MessageChannel).port1.onmessage=function(e){d(e.data)},r=function(e){i.port2.postMessage(e)}):f&&"onreadystatechange"in f.createElement("script")?(o=f.documentElement,r=function(e){var t=f.createElement("script");t.onreadystatechange=function(){d(e),t.onreadystatechange=null,o.removeChild(t),t=null},o.appendChild(t)}):r=function(e){setTimeout(d,0,e)}:(a="setImmediate$"+Math.random()+"$",s=function(t){t.source===e&&"string"==typeof t.data&&0===t.data.indexOf(a)&&d(+t.data.slice(a.length))},e.addEventListener?e.addEventListener("message",s,!1):e.attachEvent("onmessage",s),r=function(t){e.postMessage(a+t,"*")}),h.setImmediate=function(e){"function"!=typeof e&&(e=new Function(""+e));for(var t=new Array(arguments.length-1),n=0;n0?this._transform(null,t,n):n()},e.exports.decoder=c,e.exports.encoder=s},function(e,t,n){(t=e.exports=n(36)).Stream=t,t.Readable=t,t.Writable=n(41),t.Duplex=n(11),t.Transform=n(42),t.PassThrough=n(67)},function(e,t,n){"use strict";e.exports=i;var r=n(42),o=n(20);function i(e){if(!(this instanceof i))return new i(e);r.call(this,e)}o.inherits=n(14),o.inherits(i,r),i.prototype._transform=function(e,t,n){n(null,e)}},function(e,t,n){var r=n(22);function o(e){Error.call(this),Error.captureStackTrace&&Error.captureStackTrace(this,this.constructor),this.name=this.constructor.name,this.message=e||"unable to decode"}n(35).inherits(o,Error),e.exports=function(e){return function(e){e instanceof r||(e=r().append(e));var t=i(e);if(t)return e.consume(t.bytesConsumed),t.value;throw new o};function t(e,t,n){return t>=n+e}function n(e,t){return{value:e,bytesConsumed:t}}function i(e,r){r=void 0===r?0:r;var o=e.length-r;if(o<=0)return null;var i,l,f,h=e.readUInt8(r),p=0;if(!function(e,t){var n=function(e){switch(e){case 196:return 2;case 197:return 3;case 198:return 5;case 199:return 3;case 200:return 4;case 201:return 6;case 202:return 5;case 203:return 9;case 204:return 2;case 205:return 3;case 206:return 5;case 207:return 9;case 208:return 2;case 209:return 3;case 210:return 5;case 211:return 9;case 212:return 3;case 213:return 4;case 214:return 6;case 215:return 10;case 216:return 18;case 217:return 2;case 218:return 3;case 219:return 5;case 222:return 3;default:return-1}}(e);return!(-1!==n&&t=0;f--)p+=e.readUInt8(r+f+1)*Math.pow(2,8*(7-f));return n(p,9);case 208:return n(p=e.readInt8(r+1),2);case 209:return n(p=e.readInt16BE(r+1),3);case 210:return n(p=e.readInt32BE(r+1),5);case 211:return n(p=function(e,t){var n=128==(128&e[t]);if(n)for(var r=1,o=t+7;o>=t;o--){var i=(255^e[o])+r;e[o]=255&i,r=i>>8}var a=e.readUInt32BE(t+0),s=e.readUInt32BE(t+4);return(4294967296*a+s)*(n?-1:1)}(e.slice(r+1,r+9),0),9);case 202:return n(p=e.readFloatBE(r+1),5);case 203:return n(p=e.readDoubleBE(r+1),9);case 217:return t(i=e.readUInt8(r+1),o,2)?n(p=e.toString("utf8",r+2,r+2+i),2+i):null;case 218:return t(i=e.readUInt16BE(r+1),o,3)?n(p=e.toString("utf8",r+3,r+3+i),3+i):null;case 219:return t(i=e.readUInt32BE(r+1),o,5)?n(p=e.toString("utf8",r+5,r+5+i),5+i):null;case 196:return t(i=e.readUInt8(r+1),o,2)?n(p=e.slice(r+2,r+2+i),2+i):null;case 197:return t(i=e.readUInt16BE(r+1),o,3)?n(p=e.slice(r+3,r+3+i),3+i):null;case 198:return t(i=e.readUInt32BE(r+1),o,5)?n(p=e.slice(r+5,r+5+i),5+i):null;case 220:return o<3?null:(i=e.readUInt16BE(r+1),a(e,r,i,3));case 221:return o<5?null:(i=e.readUInt32BE(r+1),a(e,r,i,5));case 222:return i=e.readUInt16BE(r+1),s(e,r,i,3);case 223:throw new Error("map too big to decode in JS");case 212:return c(e,r,1);case 213:return c(e,r,2);case 214:return c(e,r,4);case 215:return c(e,r,8);case 216:return c(e,r,16);case 199:return i=e.readUInt8(r+1),l=e.readUInt8(r+2),t(i,o,3)?u(e,r,l,i,3):null;case 200:return i=e.readUInt16BE(r+1),l=e.readUInt8(r+3),t(i,o,4)?u(e,r,l,i,4):null;case 201:return i=e.readUInt32BE(r+1),l=e.readUInt8(r+5),t(i,o,6)?u(e,r,l,i,6):null}if(144==(240&h))return a(e,r,i=15&h,1);if(128==(240&h))return s(e,r,i=15&h,1);if(160==(224&h))return t(i=31&h,o,1)?n(p=e.toString("utf8",r+1,r+i+1),i+1):null;if(h>=224)return n(p=h-256,1);if(h<128)return n(h,1);throw new Error("not implemented yet")}function a(e,t,r,o){var a,s=[],c=0;for(t+=o,a=0;ai)&&((n=r.allocUnsafe(9))[0]=203,n.writeDoubleBE(e,1)),n}e.exports=function(e,t,n,i){function s(c,u){var l,f,h;if(void 0===c)throw new Error("undefined is not encodable in msgpack!");if(null===c)(l=r.allocUnsafe(1))[0]=192;else if(!0===c)(l=r.allocUnsafe(1))[0]=195;else if(!1===c)(l=r.allocUnsafe(1))[0]=194;else if("string"==typeof c)(f=r.byteLength(c))<32?((l=r.allocUnsafe(1+f))[0]=160|f,f>0&&l.write(c,1)):f<=255&&!n?((l=r.allocUnsafe(2+f))[0]=217,l[1]=f,l.write(c,2)):f<=65535?((l=r.allocUnsafe(3+f))[0]=218,l.writeUInt16BE(f,1),l.write(c,3)):((l=r.allocUnsafe(5+f))[0]=219,l.writeUInt32BE(f,1),l.write(c,5));else if(c&&(c.readUInt32LE||c instanceof Uint8Array))c instanceof Uint8Array&&(c=r.from(c)),c.length<=255?((l=r.allocUnsafe(2))[0]=196,l[1]=c.length):c.length<=65535?((l=r.allocUnsafe(3))[0]=197,l.writeUInt16BE(c.length,1)):((l=r.allocUnsafe(5))[0]=198,l.writeUInt32BE(c.length,1)),l=o([l,c]);else if(Array.isArray(c))c.length<16?(l=r.allocUnsafe(1))[0]=144|c.length:c.length<65536?((l=r.allocUnsafe(3))[0]=220,l.writeUInt16BE(c.length,1)):((l=r.allocUnsafe(5))[0]=221,l.writeUInt32BE(c.length,1)),l=c.reduce(function(e,t){return e.append(s(t,!0)),e},o().append(l));else{if(!i&&"function"==typeof c.getDate)return function(e){var t,n=1*e,i=Math.floor(n/1e3),a=1e6*(n-1e3*i);if(a||i>4294967295){(t=new r(10))[0]=215,t[1]=-1;var s=4*a,c=i/Math.pow(2,32),u=s+c&4294967295,l=4294967295&i;t.writeInt32BE(u,2),t.writeInt32BE(l,6)}else(t=new r(6))[0]=214,t[1]=-1,t.writeUInt32BE(Math.floor(n/1e3),2);return o().append(t)}(c);if("object"==typeof c)l=function(t){var n,i,a=-1,s=[];for(n=0;n>8),s.push(255&a)):(s.push(201),s.push(a>>24),s.push(a>>16&255),s.push(a>>8&255),s.push(255&a));return o().append(r.from(s)).append(i)}(c)||function(e){var t,n,i=[],a=0;for(t in e)e.hasOwnProperty(t)&&void 0!==e[t]&&"function"!=typeof e[t]&&(++a,i.push(s(t,!0)),i.push(s(e[t],!0)));a<16?(n=r.allocUnsafe(1))[0]=128|a:((n=r.allocUnsafe(3))[0]=222,n.writeUInt16BE(a,1));return i.unshift(n),i.reduce(function(e,t){return e.append(t)},o())}(c);else if("number"==typeof c){if((h=c)!==Math.floor(h))return a(c,t);if(c>=0)if(c<128)(l=r.allocUnsafe(1))[0]=c;else if(c<256)(l=r.allocUnsafe(2))[0]=204,l[1]=c;else if(c<65536)(l=r.allocUnsafe(3))[0]=205,l.writeUInt16BE(c,1);else if(c<=4294967295)(l=r.allocUnsafe(5))[0]=206,l.writeUInt32BE(c,1);else{if(!(c<=9007199254740991))return a(c,!0);(l=r.allocUnsafe(9))[0]=207,function(e,t){for(var n=7;n>=0;n--)e[n+1]=255&t,t/=256}(l,c)}else if(c>=-32)(l=r.allocUnsafe(1))[0]=256+c;else if(c>=-128)(l=r.allocUnsafe(2))[0]=208,l.writeInt8(c,1);else if(c>=-32768)(l=r.allocUnsafe(3))[0]=209,l.writeInt16BE(c,1);else if(c>-214748365)(l=r.allocUnsafe(5))[0]=210,l.writeInt32BE(c,1);else{if(!(c>=-9007199254740991))return a(c,!0);(l=r.allocUnsafe(9))[0]=211,function(e,t,n){var r=n<0;r&&(n=Math.abs(n));var o=n%4294967296,i=n/4294967296;if(e.writeUInt32BE(Math.floor(i),t+0),e.writeUInt32BE(o,t+4),r)for(var a=1,s=t+7;s>=t;s--){var c=(255^e[s])+a;e[s]=255&c,a=c>>8}}(l,1,c)}}}if(!l)throw new Error("not implemented yet");return u?l:l.slice()}return s}},function(e,t,n){"use strict";var r=this&&this.__awaiter||function(e,t,n,r){return new(n||(n=Promise))(function(o,i){function a(e){try{c(r.next(e))}catch(e){i(e)}}function s(e){try{c(r.throw(e))}catch(e){i(e)}}function c(e){e.done?o(e.value):new n(function(t){t(e.value)}).then(a,s)}c((r=r.apply(e,t||[])).next())})},o=this&&this.__generator||function(e,t){var n,r,o,i,a={label:0,sent:function(){if(1&o[0])throw o[1];return o[1]},trys:[],ops:[]};return i={next:s(0),throw:s(1),return:s(2)},"function"==typeof Symbol&&(i[Symbol.iterator]=function(){return this}),i;function s(i){return function(s){return function(i){if(n)throw new TypeError("Generator is already executing.");for(;a;)try{if(n=1,r&&(o=2&i[0]?r.return:i[0]?r.throw||((o=r.return)&&o.call(r),0):r.next)&&!(o=o.call(r,i[1])).done)return o;switch(r=0,o&&(i=[2&i[0],o.value]),i[0]){case 0:case 1:o=i;break;case 4:return a.label++,{value:i[1],done:!1};case 5:a.label++,r=i[1],i=[0];continue;case 7:i=a.ops.pop(),a.trys.pop();continue;default:if(!(o=(o=a.trys).length>0&&o[o.length-1])&&(6===i[0]||2===i[0])){a=0;continue}if(3===i[0]&&(!o||i[1]>o[0]&&i[1]this.nextBatchId?this.fatalError?(this.logger.log(s.LogLevel.Debug,"Received a new batch "+e+" but errored out on a previous batch "+(this.nextBatchId-1)),[4,n.send("OnRenderCompleted",this.nextBatchId-1,this.fatalError.toString())]):[3,4]:[3,5];case 3:return o.sent(),[2];case 4:return this.logger.log(s.LogLevel.Debug,"Waiting for batch "+this.nextBatchId+". Batch "+e+" not processed."),[2];case 5:return o.trys.push([5,7,,8]),this.nextBatchId++,this.logger.log(s.LogLevel.Debug,"Applying batch "+e+"."),i.renderBatch(this.browserRendererId,new a.OutOfProcessRenderBatch(t)),[4,this.completeBatch(n,e)];case 6:return o.sent(),[3,8];case 7:throw r=o.sent(),this.fatalError=r.toString(),this.logger.log(s.LogLevel.Error,"There was an error applying batch "+e+"."),n.send("OnRenderCompleted",e,r.toString()),r;case 8:return[2]}})})},e.prototype.getLastBatchid=function(){return this.nextBatchId-1},e.prototype.completeBatch=function(e,t){return r(this,void 0,void 0,function(){return o(this,function(n){switch(n.label){case 0:return n.trys.push([0,2,,3]),[4,e.send("OnRenderCompleted",t,null)];case 1:return n.sent(),[3,3];case 2:return n.sent(),this.logger.log(s.LogLevel.Warning,"Failed to deliver completion notification for render '"+t+"'."),[3,3];case 3:return[2]}})})},e}();t.RenderQueue=c},function(e,t,n){"use strict";Object.defineProperty(t,"__esModule",{value:!0});var r=n(72),o=Math.pow(2,32),i=Math.pow(2,21)-1,a=function(){function e(e){this.batchData=e;var t=new l(e);this.arrayRangeReader=new f(e),this.arrayBuilderSegmentReader=new h(e),this.diffReader=new s(e),this.editReader=new c(e,t),this.frameReader=new u(e,t)}return e.prototype.updatedComponents=function(){return p(this.batchData,this.batchData.length-20)},e.prototype.referenceFrames=function(){return p(this.batchData,this.batchData.length-16)},e.prototype.disposedComponentIds=function(){return p(this.batchData,this.batchData.length-12)},e.prototype.disposedEventHandlerIds=function(){return p(this.batchData,this.batchData.length-8)},e.prototype.updatedComponentsEntry=function(e,t){var n=e+4*t;return p(this.batchData,n)},e.prototype.referenceFramesEntry=function(e,t){return e+20*t},e.prototype.disposedComponentIdsEntry=function(e,t){var n=e+4*t;return p(this.batchData,n)},e.prototype.disposedEventHandlerIdsEntry=function(e,t){var n=e+8*t;return g(this.batchData,n)},e}();t.OutOfProcessRenderBatch=a;var s=function(){function e(e){this.batchDataUint8=e}return e.prototype.componentId=function(e){return p(this.batchDataUint8,e)},e.prototype.edits=function(e){return e+4},e.prototype.editsEntry=function(e,t){return e+16*t},e}(),c=function(){function e(e,t){this.batchDataUint8=e,this.stringReader=t}return e.prototype.editType=function(e){return p(this.batchDataUint8,e)},e.prototype.siblingIndex=function(e){return p(this.batchDataUint8,e+4)},e.prototype.newTreeIndex=function(e){return p(this.batchDataUint8,e+8)},e.prototype.moveToSiblingIndex=function(e){return p(this.batchDataUint8,e+8)},e.prototype.removedAttributeName=function(e){var t=p(this.batchDataUint8,e+12);return this.stringReader.readString(t)},e}(),u=function(){function e(e,t){this.batchDataUint8=e,this.stringReader=t}return e.prototype.frameType=function(e){return p(this.batchDataUint8,e)},e.prototype.subtreeLength=function(e){return p(this.batchDataUint8,e+4)},e.prototype.elementReferenceCaptureId=function(e){var t=p(this.batchDataUint8,e+4);return this.stringReader.readString(t)},e.prototype.componentId=function(e){return p(this.batchDataUint8,e+8)},e.prototype.elementName=function(e){var t=p(this.batchDataUint8,e+8);return this.stringReader.readString(t)},e.prototype.textContent=function(e){var t=p(this.batchDataUint8,e+4);return this.stringReader.readString(t)},e.prototype.markupContent=function(e){var t=p(this.batchDataUint8,e+4);return this.stringReader.readString(t)},e.prototype.attributeName=function(e){var t=p(this.batchDataUint8,e+4);return this.stringReader.readString(t)},e.prototype.attributeValue=function(e){var t=p(this.batchDataUint8,e+8);return this.stringReader.readString(t)},e.prototype.attributeEventHandlerId=function(e){return g(this.batchDataUint8,e+12)},e}(),l=function(){function e(e){this.batchDataUint8=e,this.stringTableStartIndex=p(e,e.length-4)}return e.prototype.readString=function(e){if(-1===e)return null;var t,n=p(this.batchDataUint8,this.stringTableStartIndex+4*e),o=function(e,t){for(var n=0,r=0,o=0;o<4;o++){var i=e[t+o];if(n|=(127&i)<>>0)}function g(e,t){var n=d(e,t+4);if(n>i)throw new Error("Cannot read uint64 with high order part "+n+", because the result would exceed Number.MAX_SAFE_INTEGER.");return n*o+d(e,t)}},function(e,t,n){"use strict";Object.defineProperty(t,"__esModule",{value:!0});var r="function"==typeof TextDecoder?new TextDecoder("utf-8"):null;t.decodeUtf8=r?r.decode.bind(r):function(e){var t=0,n=e.length,r=[],o=[];for(;t65535&&(u-=65536,r.push(u>>>10&1023|55296),u=56320|1023&u),r.push(u)}r.length>1024&&(o.push(String.fromCharCode.apply(null,r)),r.length=0)}return o.push(String.fromCharCode.apply(null,r)),o.join("")}},function(e,t,n){"use strict";Object.defineProperty(t,"__esModule",{value:!0});var r=n(15),o=function(){function e(){}return e.prototype.log=function(e,t){},e.instance=new e,e}();t.NullLogger=o;var i=function(){function e(e){this.minimumLogLevel=e}return e.prototype.log=function(e,t){if(e>=this.minimumLogLevel)switch(e){case r.LogLevel.Critical:case r.LogLevel.Error:console.error("["+(new Date).toISOString()+"] "+r.LogLevel[e]+": "+t);break;case r.LogLevel.Warning:console.warn("["+(new Date).toISOString()+"] "+r.LogLevel[e]+": "+t);break;case r.LogLevel.Information:console.info("["+(new Date).toISOString()+"] "+r.LogLevel[e]+": "+t);break;default:console.log("["+(new Date).toISOString()+"] "+r.LogLevel[e]+": "+t)}},e}();t.ConsoleLogger=i},function(e,t,n){"use strict";var r=this&&this.__awaiter||function(e,t,n,r){return new(n||(n=Promise))(function(o,i){function a(e){try{c(r.next(e))}catch(e){i(e)}}function s(e){try{c(r.throw(e))}catch(e){i(e)}}function c(e){e.done?o(e.value):new n(function(t){t(e.value)}).then(a,s)}c((r=r.apply(e,t||[])).next())})},o=this&&this.__generator||function(e,t){var n,r,o,i,a={label:0,sent:function(){if(1&o[0])throw o[1];return o[1]},trys:[],ops:[]};return i={next:s(0),throw:s(1),return:s(2)},"function"==typeof Symbol&&(i[Symbol.iterator]=function(){return this}),i;function s(i){return function(s){return function(i){if(n)throw new TypeError("Generator is already executing.");for(;a;)try{if(n=1,r&&(o=2&i[0]?r.return:i[0]?r.throw||((o=r.return)&&o.call(r),0):r.next)&&!(o=o.call(r,i[1])).done)return o;switch(r=0,o&&(i=[2&i[0],o.value]),i[0]){case 0:case 1:o=i;break;case 4:return a.label++,{value:i[1],done:!1};case 5:a.label++,r=i[1],i=[0];continue;case 7:i=a.ops.pop(),a.trys.pop();continue;default:if(!(o=(o=a.trys).length>0&&o[o.length-1])&&(6===i[0]||2===i[0])){a=0;continue}if(3===i[0]&&(!o||i[1]>o[0]&&i[1]0&&o[o.length-1])&&(6===i[0]||2===i[0])){a=0;continue}if(3===i[0]&&(!o||i[1]>o[0]&&i[1]0&&o[o.length-1])&&(6===i[0]||2===i[0])){a=0;continue}if(3===i[0]&&(!o||i[1]>o[0]&&i[1]reloading the page if you're unable to reconnect."},e.prototype.rejected=function(){this.button.style.display="none",this.reloadParagraph.style.display="none",this.message.innerHTML="Could not reconnect to the server. Reload the page to restore functionality."},e}();t.DefaultReconnectDisplay=a},function(e,t,n){"use strict";Object.defineProperty(t,"__esModule",{value:!0});var r=function(){function e(e){this.dialog=e}return e.prototype.show=function(){this.removeClasses(),this.dialog.classList.add(e.ShowClassName)},e.prototype.hide=function(){this.removeClasses(),this.dialog.classList.add(e.HideClassName)},e.prototype.failed=function(){this.removeClasses(),this.dialog.classList.add(e.FailedClassName)},e.prototype.rejected=function(){this.removeClasses(),this.dialog.classList.add(e.RefusedClassName)},e.prototype.removeClasses=function(){this.dialog.classList.remove(e.ShowClassName,e.HideClassName,e.FailedClassName,e.RefusedClassName)},e.ShowClassName="components-reconnect-show",e.HideClassName="components-reconnect-hide",e.FailedClassName="components-reconnect-failed",e.RefusedClassName="components-reconnect-refused",e}();t.UserSpecifiedDisplay=r},function(e,t,n){"use strict";n.r(t);var r=n(5),o=n(12),i=n(2),a=function(){function e(){}return e.write=function(e){var t=e.byteLength||e.length,n=[];do{var r=127&t;(t>>=7)>0&&(r|=128),n.push(r)}while(t>0);t=e.byteLength||e.length;var o=new Uint8Array(n.length+t);return o.set(n,0),o.set(e,n.length),o.buffer},e.parse=function(e){for(var t=[],n=new Uint8Array(e),r=[0,7,14,21,28],o=0;o7)throw new Error("Messages bigger than 2GB are not supported.");if(!(n.byteLength>=o+i+a))throw new Error("Incomplete message.");t.push(n.slice?n.slice(o+i,o+i+a):n.subarray(o+i,o+i+a)),o=o+i+a}return t},e}();var s=new Uint8Array([145,i.MessageType.Ping]),c=function(){function e(){this.name="messagepack",this.version=1,this.transferFormat=i.TransferFormat.Binary,this.errorResult=1,this.voidResult=2,this.nonVoidResult=3}return e.prototype.parseMessages=function(e,t){if(!(e instanceof r.Buffer||(n=e,n&&"undefined"!=typeof ArrayBuffer&&(n instanceof ArrayBuffer||n.constructor&&"ArrayBuffer"===n.constructor.name))))throw new Error("Invalid input for MessagePack hub protocol. Expected an ArrayBuffer or Buffer.");var n;null===t&&(t=i.NullLogger.instance);for(var o=[],s=0,c=a.parse(e);s - netstandard2.0;netcoreapp3.0 + netstandard2.0;netcoreapp5.0 @@ -9,8 +9,8 @@ - - + + diff --git a/src/Components/Web/ref/Microsoft.AspNetCore.Components.Web.netcoreapp3.0.cs b/src/Components/Web/ref/Microsoft.AspNetCore.Components.Web.netcoreapp.cs similarity index 100% rename from src/Components/Web/ref/Microsoft.AspNetCore.Components.Web.netcoreapp3.0.cs rename to src/Components/Web/ref/Microsoft.AspNetCore.Components.Web.netcoreapp.cs diff --git a/src/Components/Web/src/Microsoft.AspNetCore.Components.Web.csproj b/src/Components/Web/src/Microsoft.AspNetCore.Components.Web.csproj index 1f683d5d49..99d9ea4b9a 100644 --- a/src/Components/Web/src/Microsoft.AspNetCore.Components.Web.csproj +++ b/src/Components/Web/src/Microsoft.AspNetCore.Components.Web.csproj @@ -1,8 +1,8 @@ - netstandard2.0;netcoreapp3.0 - netcoreapp3.0 + netstandard2.0;$(DefaultNetCoreTargetFramework) + $(DefaultNetCoreTargetFramework) true Support for rendering ASP.NET Core components for browsers. true diff --git a/src/Components/Web/test/Microsoft.AspNetCore.Components.Web.Tests.csproj b/src/Components/Web/test/Microsoft.AspNetCore.Components.Web.Tests.csproj index ae732d3e10..5c8a5a0d19 100644 --- a/src/Components/Web/test/Microsoft.AspNetCore.Components.Web.Tests.csproj +++ b/src/Components/Web/test/Microsoft.AspNetCore.Components.Web.Tests.csproj @@ -1,7 +1,7 @@ - netcoreapp3.0 + $(DefaultNetCoreTargetFramework) Microsoft.AspNetCore.Components diff --git a/src/Components/test/E2ETest/Microsoft.AspNetCore.Components.E2ETests.csproj b/src/Components/test/E2ETest/Microsoft.AspNetCore.Components.E2ETests.csproj index 29f54b1df1..8368521831 100644 --- a/src/Components/test/E2ETest/Microsoft.AspNetCore.Components.E2ETests.csproj +++ b/src/Components/test/E2ETest/Microsoft.AspNetCore.Components.E2ETests.csproj @@ -4,7 +4,7 @@ - netcoreapp3.0 + $(DefaultNetCoreTargetFramework) Components.E2ETests diff --git a/src/Components/test/Ignitor.Test/Ignitor.Test.csproj b/src/Components/test/Ignitor.Test/Ignitor.Test.csproj index ad35b17b42..7f23d6804f 100644 --- a/src/Components/test/Ignitor.Test/Ignitor.Test.csproj +++ b/src/Components/test/Ignitor.Test/Ignitor.Test.csproj @@ -1,7 +1,7 @@ - netcoreapp3.0 + $(DefaultNetCoreTargetFramework) diff --git a/src/Components/test/testassets/BasicTestApp/AfterRenderInteropComponent.razor b/src/Components/test/testassets/BasicTestApp/AfterRenderInteropComponent.razor index 41bcfe275d..7092993113 100644 --- a/src/Components/test/testassets/BasicTestApp/AfterRenderInteropComponent.razor +++ b/src/Components/test/testassets/BasicTestApp/AfterRenderInteropComponent.razor @@ -1,7 +1,7 @@ @using Microsoft.JSInterop @inject IJSRuntime JSRuntime - + @code { ElementReference myInput; diff --git a/src/Components/test/testassets/BasicTestApp/ComponentRefComponent.razor b/src/Components/test/testassets/BasicTestApp/ComponentRefComponent.razor index 3670852e48..1768d2b7cc 100644 --- a/src/Components/test/testassets/BasicTestApp/ComponentRefComponent.razor +++ b/src/Components/test/testassets/BasicTestApp/ComponentRefComponent.razor @@ -11,7 +11,7 @@ @if (_toggleCapturedComponentPresence) {
- +
} diff --git a/src/Components/test/testassets/BasicTestApp/ElementRefComponent.razor b/src/Components/test/testassets/BasicTestApp/ElementRefComponent.razor index 06ece3a788..36a003b48f 100644 --- a/src/Components/test/testassets/BasicTestApp/ElementRefComponent.razor +++ b/src/Components/test/testassets/BasicTestApp/ElementRefComponent.razor @@ -17,7 +17,7 @@ @if (_toggleCapturedElementPresence) { - + }