From 244ed547644047a0696eba8955f6a407fb30a338 Mon Sep 17 00:00:00 2001 From: Javier Calvarro Nelson Date: Tue, 2 Jul 2019 12:40:50 +0200 Subject: [PATCH 01/13] 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 02/13] 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 03/13] 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 04/13] 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 05/13] 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 06/13] 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 07/13] 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 08/13] 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 09/13] 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 10/13] 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 11/13] 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 12/13] 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 13/13] 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, }; } }