diff --git a/src/Configuration.KeyPerFile/Directory.Build.props b/src/Configuration.KeyPerFile/Directory.Build.props
index 2082380096..63d0c8b102 100644
--- a/src/Configuration.KeyPerFile/Directory.Build.props
+++ b/src/Configuration.KeyPerFile/Directory.Build.props
@@ -2,7 +2,8 @@
- true
+ true
configuration
+ $(NoWarn);PKG0001
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..21f0053e59
--- /dev/null
+++ b/src/Configuration.KeyPerFile/ref/Microsoft.Extensions.Configuration.KeyPerFile.csproj
@@ -0,0 +1,11 @@
+
+
+
+ netstandard2.0
+
+
+
+
+
+
+
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/Configuration.KeyPerFile/src/KeyPerFileConfigurationProvider.cs b/src/Configuration.KeyPerFile/src/KeyPerFileConfigurationProvider.cs
index 6e4234ecf3..2e33b9dfcd 100644
--- a/src/Configuration.KeyPerFile/src/KeyPerFileConfigurationProvider.cs
+++ b/src/Configuration.KeyPerFile/src/KeyPerFileConfigurationProvider.cs
@@ -40,10 +40,8 @@ namespace Microsoft.Extensions.Configuration.KeyPerFile
Data = data;
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("/");
@@ -71,5 +69,15 @@ namespace Microsoft.Extensions.Configuration.KeyPerFile
Data = data;
}
+
+ 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/src/Microsoft.Extensions.Configuration.KeyPerFile.csproj b/src/Configuration.KeyPerFile/src/Microsoft.Extensions.Configuration.KeyPerFile.csproj
index 4eb19f3293..5bd7b2c7ef 100644
--- a/src/Configuration.KeyPerFile/src/Microsoft.Extensions.Configuration.KeyPerFile.csproj
+++ b/src/Configuration.KeyPerFile/src/Microsoft.Extensions.Configuration.KeyPerFile.csproj
@@ -3,7 +3,7 @@
Configuration provider that uses files in a directory for Microsoft.Extensions.Configuration.
netstandard2.0
- false
+ true
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..4609ee2560
--- /dev/null
+++ b/src/Configuration.KeyPerFile/test/ConfigurationProviderTestBase.cs
@@ -0,0 +1,761 @@
+// Copyright (c) .NET 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()
+ {
+ 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]
+ public virtual void Combine_after_other_provider()
+ {
+ AssertConfig(
+ BuildConfigRoot(
+ LoadUsingMemoryProvider(TestSection.MissingSection2ValuesConfig),
+ LoadThroughProvider(TestSection.MissingSection4Config)));
+
+ AssertConfig(
+ BuildConfigRoot(
+ LoadUsingMemoryProvider(TestSection.MissingSection4Config),
+ LoadThroughProvider(TestSection.MissingSection2ValuesConfig)));
+ }
+
+ [Fact]
+ public virtual void Combine_before_other_provider()
+ {
+ AssertConfig(
+ BuildConfigRoot(
+ LoadThroughProvider(TestSection.MissingSection2ValuesConfig),
+ LoadUsingMemoryProvider(TestSection.MissingSection4Config)));
+
+ AssertConfig(
+ BuildConfigRoot(
+ LoadThroughProvider(TestSection.MissingSection4Config),
+ LoadUsingMemoryProvider(TestSection.MissingSection2ValuesConfig)));
+ }
+
+ [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 AssertDebugView(
+ IConfigurationRoot config,
+ string expected)
+ {
+ 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("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 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("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);
+
+ 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);
+
+ 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 (AsArray == 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 MissingSection2ValuesConfig { 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[]
+ {
+ ("Key3a", (TestKeyValue)new[] {"ArrayValue0"})
+ },
+ })
+ }
+ }),
+ ("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")}
+ })
+ }
+ })
+ }
+ };
+
+ 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 499c25106c..838e62222d 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 System.Threading;
using System.Threading.Tasks;
@@ -195,7 +197,15 @@ namespace Microsoft.Extensions.Configuration.KeyPerFile.Test
using (var cts = new CancellationTokenSource(TimeSpan.FromMilliseconds(250)))
{
- _ = Task.Run(() => { while (!cts.IsCancellationRequested) config.Reload(); });
+ void ReloadLoop()
+ {
+ while (!cts.IsCancellationRequested)
+ {
+ config.Reload();
+ }
+ }
+
+ _ = Task.Run(ReloadLoop);
while (!cts.IsCancellationRequested)
{
@@ -223,20 +233,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
@@ -248,75 +249,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)
{
@@ -337,7 +296,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));
}
}
}
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 154fd5bb62..aee7e7a7bf 100644
--- a/src/Configuration.KeyPerFile/test/Microsoft.Extensions.Configuration.KeyPerFile.Tests.csproj
+++ b/src/Configuration.KeyPerFile/test/Microsoft.Extensions.Configuration.KeyPerFile.Tests.csproj
@@ -1,11 +1,14 @@
- netcoreapp2.2;net461
+ netcoreapp3.0;net472
-
+
+
+
+
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/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/ref/Microsoft.Extensions.FileProviders.Embedded.csproj b/src/FileProviders/Embedded/ref/Microsoft.Extensions.FileProviders.Embedded.csproj
new file mode 100644
index 0000000000..b8f2f33387
--- /dev/null
+++ b/src/FileProviders/Embedded/ref/Microsoft.Extensions.FileProviders.Embedded.csproj
@@ -0,0 +1,10 @@
+
+
+
+ netstandard2.0
+
+
+
+
+
+
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/FileProviders/Embedded/src/Microsoft.Extensions.FileProviders.Embedded.csproj b/src/FileProviders/Embedded/src/Microsoft.Extensions.FileProviders.Embedded.csproj
index 191330625a..7d84c19f9d 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
+
+
+
+
@@ -17,35 +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=..\..\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;
-
+ $(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 0cc5ed823a..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$
@@ -25,9 +15,7 @@
-
-
-
-
+
+
-
\ No newline at end of file
+
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/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/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..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'">netstandard1.5
- <_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
- netcoreapp2.2;net461
+ netcoreapp3.0;net472
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 e6c42b46f1..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
@@ -3,24 +3,16 @@
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
+ true
false
false
-
-
-
-
-
-
-
-
-
-
+
+
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 51%
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
index 8be54bf05d..ed68958fe8 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.Tests.csproj
@@ -1,7 +1,7 @@
- netcoreapp2.2;net461
+ netcoreapp3.0
@@ -9,16 +9,8 @@
-
-
-
-
-
-
-
-
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/ref/Microsoft.Extensions.Diagnostics.HealthChecks.Abstractions.csproj b/src/HealthChecks/Abstractions/ref/Microsoft.Extensions.Diagnostics.HealthChecks.Abstractions.csproj
new file mode 100644
index 0000000000..be23858955
--- /dev/null
+++ b/src/HealthChecks/Abstractions/ref/Microsoft.Extensions.Diagnostics.HealthChecks.Abstractions.csproj
@@ -0,0 +1,10 @@
+
+
+
+ netstandard2.0
+
+
+
+
+
+
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..9ab497257e
--- /dev/null
+++ b/src/HealthChecks/Abstractions/ref/Microsoft.Extensions.Diagnostics.HealthChecks.Abstractions.netstandard2.0.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.
+
+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 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/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/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);
}
///
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..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,6 +11,7 @@ Microsoft.Extensions.Diagnostics.HealthChecks.IHealthCheck
$(NoWarn);CS1591
true
diagnostics;healthchecks
+ 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/ref/Microsoft.Extensions.Diagnostics.HealthChecks.csproj b/src/HealthChecks/HealthChecks/ref/Microsoft.Extensions.Diagnostics.HealthChecks.csproj
new file mode 100644
index 0000000000..277e60910f
--- /dev/null
+++ b/src/HealthChecks/HealthChecks/ref/Microsoft.Extensions.Diagnostics.HealthChecks.csproj
@@ -0,0 +1,12 @@
+
+
+
+ netstandard2.0
+
+
+
+
+
+
+
+
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..a23961efdd
--- /dev/null
+++ b/src/HealthChecks/HealthChecks/ref/Microsoft.Extensions.Diagnostics.HealthChecks.netstandard2.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/DefaultHealthCheckService.cs b/src/HealthChecks/HealthChecks/src/DefaultHealthCheckService.cs
index d5d71d9cb4..c2c9084e0a 100644
--- a/src/HealthChecks/HealthChecks/src/DefaultHealthCheckService.cs
+++ b/src/HealthChecks/HealthChecks/src/DefaultHealthCheckService.cs
@@ -40,74 +40,115 @@ namespace Microsoft.Extensions.Diagnostics.HealthChecks
CancellationToken cancellationToken = default)
{
var registrations = _options.Value.Registrations;
+ if (predicate != null)
+ {
+ registrations = registrations.Where(predicate).ToArray();
+ }
+ var totalTime = ValueStopwatch.StartNew();
+ Log.HealthCheckProcessingBegin(_logger);
+
+ var tasks = new Task[registrations.Count];
+ var index = 0;
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;
- }
+ tasks[index++] = Task.Run(() => RunCheckAsync(scope, registration, cancellationToken), cancellationToken);
}
- var totalElapsedTime = totalTime.GetElapsedTime();
- var report = new HealthReport(entries, totalElapsedTime);
- Log.HealthCheckProcessingEnd(_logger, report.Status, totalElapsedTime);
- return report;
+ await Task.WhenAll(tasks).ConfigureAwait(false);
+ }
+
+ index = 0;
+ var entries = new Dictionary(StringComparer.OrdinalIgnoreCase);
+ foreach (var registration in registrations)
+ {
+ entries[registration.Name] = tasks[index++].Result;
+ }
+
+ var totalElapsedTime = totalTime.GetElapsedTime();
+ var report = new HealthReport(entries, totalElapsedTime);
+ Log.HealthCheckProcessingEnd(_logger, report.Status, totalElapsedTime);
+ return report;
+ }
+
+ private async Task RunCheckAsync(IServiceScope scope, HealthCheckRegistration registration, CancellationToken cancellationToken)
+ {
+ 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;
+ CancellationTokenSource timeoutCancellationTokenSource = null;
+ try
+ {
+ 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).ConfigureAwait(false);
+
+ 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);
+ }
+ 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();
+ entry = new HealthReportEntry(
+ status: HealthStatus.Unhealthy,
+ description: ex.Message,
+ duration: duration,
+ exception: ex,
+ data: null);
+
+ Log.HealthCheckError(_logger, registration, ex, duration);
+ }
+
+ finally
+ {
+ timeoutCancellationTokenSource?.Dispose();
+ }
+
+ return entry;
}
}
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/src/DependencyInjection/HealthChecksBuilderAddCheckExtensions.cs b/src/HealthChecks/HealthChecks/src/DependencyInjection/HealthChecksBuilderAddCheckExtensions.cs
index 9508889054..51b7815438 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
@@ -113,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);
}
///
@@ -148,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);
}
///
@@ -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/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;
}
}
diff --git a/src/HealthChecks/HealthChecks/src/Microsoft.Extensions.Diagnostics.HealthChecks.csproj b/src/HealthChecks/HealthChecks/src/Microsoft.Extensions.Diagnostics.HealthChecks.csproj
index d0b1c97ef0..463e5b3632 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/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/DefaultHealthCheckServiceTest.cs b/src/HealthChecks/HealthChecks/test/DefaultHealthCheckServiceTest.cs
index 9ab991204e..38442edb93 100644
--- a/src/HealthChecks/HealthChecks/test/DefaultHealthCheckServiceTest.cs
+++ b/src/HealthChecks/HealthChecks/test/DefaultHealthCheckServiceTest.cs
@@ -6,7 +6,9 @@ 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.Internal;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Logging.Testing;
using Microsoft.Extensions.Options;
@@ -375,6 +377,113 @@ namespace Microsoft.Extensions.Diagnostics.HealthChecks
});
}
+ [Fact]
+ public async Task CheckHealthAsync_ChecksAreRunInParallel()
+ {
+ // Arrange
+ var input1 = new TaskCompletionSource
+
+
+
+
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
new file mode 100644
index 0000000000..4c82321255
--- /dev/null
+++ b/src/JSInterop/Microsoft.JSInterop.JS/src/package-lock.json
@@ -0,0 +1,348 @@
+{
+ "name": "@dotnet/jsinterop",
+ "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",
+ "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"
+ }
+ },
+ "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",
+ "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.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.3.0",
+ "wrappy": "1"
+ }
+ },
+ "inherits": {
+ "version": "2.0.3",
+ "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz",
+ "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.7"
+ }
+ },
+ "once": {
+ "version": "1.4.0",
+ "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz",
+ "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=",
+ "dev": true,
+ "requires": {
+ "wrappy": "1"
+ }
+ },
+ "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
+ },
+ "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.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",
+ "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=",
+ "dev": true
+ }
+ }
+}
diff --git a/src/JSInterop/Microsoft.JSInterop.JS/src/package.json b/src/JSInterop/Microsoft.JSInterop.JS/src/package.json
new file mode 100644
index 0000000000..8be950e16f
--- /dev/null
+++ b/src/JSInterop/Microsoft.JSInterop.JS/src/package.json
@@ -0,0 +1,31 @@
+{
+ "name": "@dotnet/jsinterop",
+ "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": {
+ "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": "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",
+ "tslint": "^5.9.1",
+ "typescript": "^2.7.1"
+ }
+}
diff --git a/src/JSInterop/Microsoft.JSInterop.JS/src/src/Microsoft.JSInterop.ts b/src/JSInterop/Microsoft.JSInterop.JS/src/src/Microsoft.JSInterop.ts
new file mode 100644
index 0000000000..4b5d409d0f
--- /dev/null
+++ b/src/JSInterop/Microsoft.JSInterop.JS/src/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/Microsoft.JSInterop.JS/src/tsconfig.json b/src/JSInterop/Microsoft.JSInterop.JS/src/tsconfig.json
new file mode 100644
index 0000000000..f5a2b0e31a
--- /dev/null
+++ b/src/JSInterop/Microsoft.JSInterop.JS/src/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/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/Microsoft.JSInterop/ref/Microsoft.JSInterop.csproj b/src/JSInterop/Microsoft.JSInterop/ref/Microsoft.JSInterop.csproj
new file mode 100644
index 0000000000..87fd913427
--- /dev/null
+++ b/src/JSInterop/Microsoft.JSInterop/ref/Microsoft.JSInterop.csproj
@@ -0,0 +1,10 @@
+
+
+
+ netstandard2.0
+
+
+
+
+
+
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..95b9b7956c
--- /dev/null
+++ b/src/JSInterop/Microsoft.JSInterop/ref/Microsoft.JSInterop.netstandard2.0.cs
@@ -0,0 +1,75 @@
+// Copyright (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 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/Microsoft.JSInterop/src/DotNetDispatcher.cs b/src/JSInterop/Microsoft.JSInterop/src/DotNetDispatcher.cs
new file mode 100644
index 0000000000..ac936e670a
--- /dev/null
+++ b/src/JSInterop/Microsoft.JSInterop/src/DotNetDispatcher.cs
@@ -0,0 +1,286 @@
+// Copyright (c) .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.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);
+
+ // Using ExceptionDispatchInfo here throughout because we want to always preserve
+ // original stack traces.
+ object syncResult = null;
+ ExceptionDispatchInfo syncException = null;
+
+ try
+ {
+ syncResult = InvokeSynchronously(assemblyName, methodIdentifier, targetInstance, argsJson);
+ }
+ catch (Exception ex)
+ {
+ syncException = ExceptionDispatchInfo.Capture(ex);
+ }
+
+ // If there was no callId, the caller does not want to be notified about the result
+ if (callId == null)
+ {
+ 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 =>
+ {
+ if (t.Exception != null)
+ {
+ var exception = t.Exception.GetBaseException();
+ jsRuntimeBaseInstance.EndInvokeDotNet(callId, false, ExceptionDispatchInfo.Capture(exception));
+ }
+
+ var result = TaskGenericsUtil.GetTaskResult(task);
+ jsRuntimeBaseInstance.EndInvokeDotNet(callId, true, result);
+ }, TaskScheduler.Current);
+ }
+ else
+ {
+ jsRuntimeBaseInstance.EndInvokeDotNet(callId, true, 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 (TargetInvocationException tie) when (tie.InnerException != null)
+ {
+ ExceptionDispatchInfo.Capture(tie.InnerException).Throw();
+ throw null; // unreachable
+ }
+ }
+
+ ///
+ /// 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}'.");
+ }
+ }
+}
diff --git a/src/JSInterop/Microsoft.JSInterop/src/DotNetObjectRef.cs b/src/JSInterop/Microsoft.JSInterop/src/DotNetObjectRef.cs
new file mode 100644
index 0000000000..aa62bee341
--- /dev/null
+++ b/src/JSInterop/Microsoft.JSInterop/src/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/Microsoft.JSInterop/src/ICustomArgSerializer.cs b/src/JSInterop/Microsoft.JSInterop/src/ICustomArgSerializer.cs
new file mode 100644
index 0000000000..f4012af8e9
--- /dev/null
+++ b/src/JSInterop/Microsoft.JSInterop/src/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/Microsoft.JSInterop/src/IJSInProcessRuntime.cs b/src/JSInterop/Microsoft.JSInterop/src/IJSInProcessRuntime.cs
new file mode 100644
index 0000000000..cae5126db2
--- /dev/null
+++ b/src/JSInterop/Microsoft.JSInterop/src/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/Microsoft.JSInterop/src/IJSRuntime.cs b/src/JSInterop/Microsoft.JSInterop/src/IJSRuntime.cs
new file mode 100644
index 0000000000..b56d1f0089
--- /dev/null
+++ b/src/JSInterop/Microsoft.JSInterop/src/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/Microsoft.JSInterop/src/InteropArgSerializerStrategy.cs b/src/JSInterop/Microsoft.JSInterop/src/InteropArgSerializerStrategy.cs
new file mode 100644
index 0000000000..663c1cf85a
--- /dev/null
+++ b/src/JSInterop/Microsoft.JSInterop/src/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/Microsoft.JSInterop/src/JSAsyncCallResult.cs b/src/JSInterop/Microsoft.JSInterop/src/JSAsyncCallResult.cs
new file mode 100644
index 0000000000..d46517eddc
--- /dev/null
+++ b/src/JSInterop/Microsoft.JSInterop/src/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/Microsoft.JSInterop/src/JSException.cs b/src/JSInterop/Microsoft.JSInterop/src/JSException.cs
new file mode 100644
index 0000000000..2929f69311
--- /dev/null
+++ b/src/JSInterop/Microsoft.JSInterop/src/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/Microsoft.JSInterop/src/JSInProcessRuntimeBase.cs b/src/JSInterop/Microsoft.JSInterop/src/JSInProcessRuntimeBase.cs
new file mode 100644
index 0000000000..49a47d0595
--- /dev/null
+++ b/src/JSInterop/Microsoft.JSInterop/src/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/Microsoft.JSInterop/src/JSInvokableAttribute.cs b/src/JSInterop/Microsoft.JSInterop/src/JSInvokableAttribute.cs
new file mode 100644
index 0000000000..e037078cba
--- /dev/null
+++ b/src/JSInterop/Microsoft.JSInterop/src/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/Microsoft.JSInterop/src/JSRuntime.cs b/src/JSInterop/Microsoft.JSInterop/src/JSRuntime.cs
new file mode 100644
index 0000000000..ae097ca68e
--- /dev/null
+++ b/src/JSInterop/Microsoft.JSInterop/src/JSRuntime.cs
@@ -0,0 +1,30 @@
+// Copyright (c) .NET 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 readonly AsyncLocal _currentJSRuntime = new AsyncLocal();
+
+ internal 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/Microsoft.JSInterop/src/JSRuntimeBase.cs b/src/JSInterop/Microsoft.JSInterop/src/JSRuntimeBase.cs
new file mode 100644
index 0000000000..d18bc7f4fe
--- /dev/null
+++ b/src/JSInterop/Microsoft.JSInterop/src/JSRuntimeBase.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 System;
+using System.Collections.Concurrent;
+using System.Runtime.ExceptionServices;
+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();
+ }
+ 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)
+ 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/Microsoft.JSInterop/src/Json/CamelCase.cs b/src/JSInterop/Microsoft.JSInterop/src/Json/CamelCase.cs
new file mode 100644
index 0000000000..8caae1387b
--- /dev/null
+++ b/src/JSInterop/Microsoft.JSInterop/src/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/Microsoft.JSInterop/src/Json/Json.cs b/src/JSInterop/Microsoft.JSInterop/src/Json/Json.cs
new file mode 100644
index 0000000000..7275dfe427
--- /dev/null
+++ b/src/JSInterop/Microsoft.JSInterop/src/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/Microsoft.JSInterop/src/Json/SimpleJson/README.txt b/src/JSInterop/Microsoft.JSInterop/src/Json/SimpleJson/README.txt
new file mode 100644
index 0000000000..5e58eb7106
--- /dev/null
+++ b/src/JSInterop/Microsoft.JSInterop/src/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/Microsoft.JSInterop/src/Json/SimpleJson/SimpleJson.cs b/src/JSInterop/Microsoft.JSInterop/src/Json/SimpleJson/SimpleJson.cs
new file mode 100644
index 0000000000..d12c6fae30
--- /dev/null
+++ b/src/JSInterop/Microsoft.JSInterop/src/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