Merge branch 'master' into merge/release/2.2-to-master\n\nCommit migrated from ba9e5a821b
This commit is contained in:
commit
fa96813652
|
|
@ -2,7 +2,8 @@
|
|||
<Import Project="$([MSBuild]::GetDirectoryNameOfFileAbove($(MSBuildThisFileDirectory)..\, Directory.Build.props))\Directory.Build.props" />
|
||||
|
||||
<PropertyGroup>
|
||||
<GenerateDocumentationFile Condition=" '$(IsTestProject)' != 'true' AND '$(IsSampleProject)' != 'true' ">true</GenerateDocumentationFile>
|
||||
<GenerateDocumentationFile Condition=" '$(IsUnitTestProject)' != 'true' AND '$(IsSampleProject)' != 'true' ">true</GenerateDocumentationFile>
|
||||
<PackageTags>configuration</PackageTags>
|
||||
<NoWarn>$(NoWarn);PKG0001</NoWarn>
|
||||
</PropertyGroup>
|
||||
</Project>
|
||||
|
|
|
|||
|
|
@ -0,0 +1,11 @@
|
|||
<!-- This file is automatically generated. -->
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<PropertyGroup>
|
||||
<TargetFrameworks>netstandard2.0</TargetFrameworks>
|
||||
</PropertyGroup>
|
||||
<ItemGroup Condition="'$(TargetFramework)' == 'netstandard2.0'">
|
||||
<Compile Include="Microsoft.Extensions.Configuration.KeyPerFile.netstandard2.0.cs" />
|
||||
<Reference Include="Microsoft.Extensions.Configuration" />
|
||||
<Reference Include="Microsoft.Extensions.FileProviders.Physical" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
|
|
@ -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<Microsoft.Extensions.Configuration.KeyPerFile.KeyPerFileConfigurationSource> 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<string, bool> 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; }
|
||||
}
|
||||
}
|
||||
|
|
@ -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 ?? "<Unknown>";
|
||||
|
||||
/// <summary>
|
||||
/// Generates a string representing this provider name and relevant details.
|
||||
/// </summary>
|
||||
/// <returns> The configuration name. </returns>
|
||||
public override string ToString()
|
||||
=> $"{GetType().Name} for files in '{GetDirectoryName()}' ({(Source.Optional ? "Optional" : "Required")})";
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@
|
|||
<PropertyGroup>
|
||||
<Description>Configuration provider that uses files in a directory for Microsoft.Extensions.Configuration.</Description>
|
||||
<TargetFramework>netstandard2.0</TargetFramework>
|
||||
<EnableApiCheck>false</EnableApiCheck>
|
||||
<IsShipping>true</IsShipping>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
|
|
|||
|
|
@ -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<IFileInfo>();
|
||||
SectionToTestFiles(testFiles, "", testConfig);
|
||||
|
||||
var provider = new KeyPerFileConfigurationProvider(
|
||||
new KeyPerFileConfigurationSource
|
||||
{
|
||||
Optional = true,
|
||||
FileProvider = new TestFileProvider(testFiles.ToArray())
|
||||
});
|
||||
|
||||
return (provider, () => { });
|
||||
}
|
||||
|
||||
private void SectionToTestFiles(List<IFileInfo> 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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<AsOptions>();
|
||||
|
||||
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<KeyValuePair<string, string>>();
|
||||
SectionToValues(testConfig, "", values);
|
||||
|
||||
return (new MemoryConfigurationProvider(
|
||||
new MemoryConfigurationSource
|
||||
{
|
||||
InitialData = values
|
||||
}),
|
||||
() => { });
|
||||
}
|
||||
|
||||
protected static void SectionToValues(
|
||||
TestSection section,
|
||||
string sectionName,
|
||||
IList<KeyValuePair<string, string>> values)
|
||||
{
|
||||
foreach (var tuple in section.Values.SelectMany(e => e.Value.Expand(e.Key)))
|
||||
{
|
||||
values.Add(new KeyValuePair<string, string>(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")}
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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<IFileInfo>(files);
|
||||
}
|
||||
|
||||
public bool Exists
|
||||
{
|
||||
get
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
public bool Exists => true;
|
||||
|
||||
public IEnumerator<IFileInfo> GetEnumerator()
|
||||
{
|
||||
return _list.GetEnumerator();
|
||||
}
|
||||
public IEnumerator<IFileInfo> 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));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,11 +1,14 @@
|
|||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFrameworks>netcoreapp2.2;net461</TargetFrameworks>
|
||||
<TargetFrameworks>netcoreapp3.0;net472</TargetFrameworks>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Reference Include="Microsoft.Extensions.Configuration.Binder" />
|
||||
<ProjectReference Include="..\..\Config\test\Microsoft.Extensions.Configuration.Tests.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Reference Include="Microsoft.Extensions.Configuration.KeyPerFile" />
|
||||
</ItemGroup>
|
||||
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
<Import Project="$([MSBuild]::GetDirectoryNameOfFileAbove($(MSBuildThisFileDirectory)..\, Directory.Build.props))\Directory.Build.props" />
|
||||
|
||||
<PropertyGroup>
|
||||
<GenerateDocumentationFile Condition=" '$(IsTestProject)' != 'true' ">true</GenerateDocumentationFile>
|
||||
<GenerateDocumentationFile Condition=" '$(IsUnitTestProject)' != 'true' ">true</GenerateDocumentationFile>
|
||||
<PackageTags>files;filesystem</PackageTags>
|
||||
</PropertyGroup>
|
||||
</Project>
|
||||
|
|
|
|||
|
|
@ -1,7 +0,0 @@
|
|||
<Project>
|
||||
<Import Project="$([MSBuild]::GetDirectoryNameOfFileAbove($(MSBuildThisFileDirectory)..\, Directory.Build.props))\Directory.Build.props" />
|
||||
|
||||
<PropertyGroup>
|
||||
<IsProductComponent>true</IsProductComponent>
|
||||
</PropertyGroup>
|
||||
</Project>
|
||||
|
|
@ -0,0 +1,10 @@
|
|||
<!-- This file is automatically generated. -->
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<PropertyGroup>
|
||||
<TargetFrameworks>netstandard2.0</TargetFrameworks>
|
||||
</PropertyGroup>
|
||||
<ItemGroup Condition="'$(TargetFramework)' == 'netstandard2.0'">
|
||||
<Compile Include="Microsoft.Extensions.FileProviders.Embedded.netstandard2.0.cs" />
|
||||
<Reference Include="Microsoft.Extensions.FileProviders.Abstractions" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
|
|
@ -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; }
|
||||
}
|
||||
}
|
||||
|
|
@ -5,8 +5,13 @@
|
|||
<Description>File provider for files in embedded resources for Microsoft.Extensions.FileProviders.</Description>
|
||||
<TargetFramework>netstandard2.0</TargetFramework>
|
||||
<NuspecFile>$(MSBuildProjectName).nuspec</NuspecFile>
|
||||
<IsShipping>true</IsShipping>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<InternalsVisibleTo Include="Microsoft.Extensions.FileProviders.Embedded.Tests" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Reference Include="Microsoft.Extensions.FileProviders.Abstractions" />
|
||||
<ProjectReference Include="..\..\Manifest.MSBuildTask\src\Microsoft.Extensions.FileProviders.Embedded.Manifest.Task.csproj" PrivateAssets="All" ReferenceOutputAssembly="false" />
|
||||
|
|
@ -17,35 +22,22 @@
|
|||
<SignedPackageFile Include="Microsoft.Extensions.FileProviders.Embedded.Manifest.Task.dll" Certificate="$(AssemblySigningCertName)" />
|
||||
</ItemGroup>
|
||||
|
||||
<Target Name="PopulateNuspec" BeforeTargets="GenerateNuspec" DependsOnTargets="BuiltProjectOutputGroup;DocumentationProjectOutputGroup;DebugSymbolsProjectOutputGroup;">
|
||||
|
||||
<Target Name="PopulateNuspec" BeforeTargets="InitializeStandardNuspecProperties" DependsOnTargets="BuiltProjectOutputGroup;DocumentationProjectOutputGroup;DebugSymbolsProjectOutputGroup;">
|
||||
<PropertyGroup>
|
||||
<NuspecProperties>
|
||||
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);
|
||||
|
||||
<!-- Include the assembly and symbols from the tasks project -->
|
||||
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;
|
||||
</NuspecProperties>
|
||||
<PackageTags>$(PackageTags.Replace(';',' '))</PackageTags>
|
||||
<_OutputBinary>@(BuiltProjectOutputGroupOutput)</_OutputBinary>
|
||||
<_OutputSymbol>@(DebugSymbolsProjectOutputGroupOutput)</_OutputSymbol>
|
||||
<_OutputDocumentation>@(DocumentationProjectOutputGroupOutput)</_OutputDocumentation>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<NuspecProperty Include="targetframework=$(TargetFramework)"/>
|
||||
<NuspecProperty Include="AssemblyName=$(AssemblyName)"/>
|
||||
<NuspecProperty Include="OutputBinary=$(_OutputBinary)"/>
|
||||
<NuspecProperty Include="OutputSymbol=$(_OutputSymbol)"/>
|
||||
<NuspecProperty Include="OutputDocumentation=$(_OutputDocumentation)"/>
|
||||
<!-- Include the assembly and symbols from the tasks project -->
|
||||
<NuspecProperty Include="TaskAssemblyNetStandard=$(ArtifactsDir)bin\$(AssemblyName).Manifest.Task\$(Configuration)\netstandard2.0\$(AssemblyName).Manifest.Task.dll"/>
|
||||
<NuspecProperty Include="TaskSymbolNetStandard=$(ArtifactsDir)bin\$(AssemblyName).Manifest.Task\$(Configuration)\netstandard2.0\$(AssemblyName).Manifest.Task.pdb"/>
|
||||
</ItemGroup>
|
||||
</Target>
|
||||
|
||||
</Project>
|
||||
|
|
|
|||
|
|
@ -1,17 +1,7 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<package xmlns="http://schemas.microsoft.com/packaging/2012/06/nuspec.xsd">
|
||||
<metadata>
|
||||
<id>$id$</id>
|
||||
<version>$version$</version>
|
||||
<authors>$authors$</authors>
|
||||
<requireLicenseAcceptance>true</requireLicenseAcceptance>
|
||||
<licenseUrl>$licenseUrl$</licenseUrl>
|
||||
<projectUrl>$projectUrl$</projectUrl>
|
||||
<iconUrl>$iconUrl$</iconUrl>
|
||||
<description>$description$</description>
|
||||
<copyright>$copyright$</copyright>
|
||||
<tags>$tags$</tags>
|
||||
<repository type="git" url="$repositoryUrl$" commit="$repositoryCommit$" />
|
||||
$CommonMetadataElements$
|
||||
<dependencies>
|
||||
<group targetFramework=".NETStandard2.0">
|
||||
<dependency id="Microsoft.Extensions.FileProviders.Abstractions" version="$version$" exclude="Build,Analyzers" />
|
||||
|
|
@ -25,9 +15,7 @@
|
|||
<file src="$OutputDocumentation$" target="lib\$targetframework$\" />
|
||||
<file src="build\**\*" target="build\" />
|
||||
<file src="buildMultiTargeting\**\*" target="buildMultiTargeting\" />
|
||||
<file src="$TaskAssemblyNetStandard$" target="tasks\netstandard1.5\$AssemblyName$.Manifest.Task.dll" />
|
||||
<file src="$TaskSymbolNetStandard$" target="tasks\netstandard1.5\$AssemblyName$.Manifest.Task.pdb" />
|
||||
<file src="$TaskAssemblyNet461$" target="tasks\net461\$AssemblyName$.Manifest.Task.dll" />
|
||||
<file src="$TaskSymbolNet461$" target="tasks\net461\$AssemblyName$.Manifest.Task.pdb" />
|
||||
<file src="$TaskAssemblyNetStandard$" target="tasks\netstandard2.0\$AssemblyName$.Manifest.Task.dll" />
|
||||
<file src="$TaskSymbolNetStandard$" target="tasks\netstandard2.0\$AssemblyName$.Manifest.Task.pdb" />
|
||||
</files>
|
||||
</package>
|
||||
</package>
|
||||
|
|
|
|||
|
|
@ -1,4 +0,0 @@
|
|||
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
[assembly: InternalsVisibleTo("Microsoft.Extensions.FileProviders.Embedded.Tests, PublicKey=0024000004800000940000000602000000240000525341310004000001000100f33a29044fa9d740c9b3213a93e57c84b472c84e0b8a0e1ae48e67a9f8f6de9d5f7f3d52ac23e48ac51801f1dc950abe901da34d2a9e3baadb141a17c77ef3c565dd5ee5054b91cf63bb3c6ab83f72ab3aafe93d0fc3c2348b764fafb0b1c0733de51459aeab46580384bf9d74c4e28164b7cde247f891ba07891c9d872ad2bb")]
|
||||
|
|
@ -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": []
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
@ -5,9 +5,7 @@
|
|||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup>
|
||||
<_FileProviderTaskFolder Condition="'$(MSBuildRuntimeType)' == 'Core'">netstandard1.5</_FileProviderTaskFolder>
|
||||
<_FileProviderTaskFolder Condition="'$(MSBuildRuntimeType)' != 'Core'">net461</_FileProviderTaskFolder>
|
||||
<_FileProviderTaskAssembly>$(MSBuildThisFileDirectory)..\..\tasks\$(_FileProviderTaskFolder)\Microsoft.Extensions.FileProviders.Embedded.Manifest.Task.dll</_FileProviderTaskAssembly>
|
||||
<_FileProviderTaskAssembly>$(MSBuildThisFileDirectory)..\..\tasks\netstandard2.0\Microsoft.Extensions.FileProviders.Embedded.Manifest.Task.dll</_FileProviderTaskAssembly>
|
||||
</PropertyGroup>
|
||||
|
||||
<UsingTask
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFrameworks>netcoreapp2.2;net461</TargetFrameworks>
|
||||
<TargetFrameworks>netcoreapp3.0;net472</TargetFrameworks>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
|
|
|||
|
|
@ -3,24 +3,16 @@
|
|||
<PropertyGroup>
|
||||
<Description>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.</Description>
|
||||
<TargetFrameworks>netstandard1.5;net461</TargetFrameworks>
|
||||
<TargetFramework>netstandard2.0</TargetFramework>
|
||||
<GenerateDocumentationFile>false</GenerateDocumentationFile>
|
||||
<EnableApiCheck>false</EnableApiCheck>
|
||||
<IsShippingAssembly>true</IsShippingAssembly>
|
||||
<IsPackable>false</IsPackable>
|
||||
<IsImplementationProject>false</IsImplementationProject>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Reference Include="Microsoft.Build" PrivateAssets="All" />
|
||||
<Reference Include="Microsoft.Build.Framework" PrivateAssets="All" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup Condition="'$(TargetFramework)' == 'netstandard1.5'">
|
||||
<Reference Include="Microsoft.Build.Utilities.Core" PrivateAssets="All" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup Condition="'$(TargetFramework)' == 'net461'">
|
||||
<Reference Include="Microsoft.Build.Utilities.v4.0" PrivateAssets="All" />
|
||||
<Reference Include="Microsoft.Build.Framework" />
|
||||
<Reference Include="Microsoft.Build.Utilities.Core" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFrameworks>netcoreapp2.2;net461</TargetFrameworks>
|
||||
<TargetFramework>netcoreapp3.0</TargetFramework>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
|
@ -9,16 +9,8 @@
|
|||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Reference Include="Microsoft.Build" />
|
||||
<Reference Include="Microsoft.Build.Framework" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup Condition="'$(TargetFrameworkIdentifier)' != '.NETFramework'">
|
||||
<Reference Include="Microsoft.Build.Utilities.Core" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup Condition="'$(TargetFrameworkIdentifier)' == '.NETFramework'">
|
||||
<Reference Include="Microsoft.Build.Utilities.v4.0" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
|
@ -1,7 +0,0 @@
|
|||
<Project>
|
||||
<Import Project="$([MSBuild]::GetDirectoryNameOfFileAbove($(MSBuildThisFileDirectory)..\, Directory.Build.props))\Directory.Build.props" />
|
||||
|
||||
<PropertyGroup>
|
||||
<IsProductComponent>true</IsProductComponent>
|
||||
</PropertyGroup>
|
||||
</Project>
|
||||
|
|
@ -0,0 +1,10 @@
|
|||
<!-- This file is automatically generated. -->
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<PropertyGroup>
|
||||
<TargetFrameworks>netstandard2.0</TargetFrameworks>
|
||||
</PropertyGroup>
|
||||
<ItemGroup Condition="'$(TargetFramework)' == 'netstandard2.0'">
|
||||
<Compile Include="Microsoft.Extensions.Diagnostics.HealthChecks.Abstractions.netstandard2.0.cs" />
|
||||
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
|
|
@ -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<string> tags) { }
|
||||
public HealthCheckRegistration(string name, Microsoft.Extensions.Diagnostics.HealthChecks.IHealthCheck instance, Microsoft.Extensions.Diagnostics.HealthChecks.HealthStatus? failureStatus, System.Collections.Generic.IEnumerable<string> tags, System.TimeSpan? timeout) { }
|
||||
public HealthCheckRegistration(string name, System.Func<System.IServiceProvider, Microsoft.Extensions.Diagnostics.HealthChecks.IHealthCheck> factory, Microsoft.Extensions.Diagnostics.HealthChecks.HealthStatus? failureStatus, System.Collections.Generic.IEnumerable<string> tags) { }
|
||||
public HealthCheckRegistration(string name, System.Func<System.IServiceProvider, Microsoft.Extensions.Diagnostics.HealthChecks.IHealthCheck> factory, Microsoft.Extensions.Diagnostics.HealthChecks.HealthStatus? failureStatus, System.Collections.Generic.IEnumerable<string> tags, System.TimeSpan? timeout) { }
|
||||
public System.Func<System.IServiceProvider, Microsoft.Extensions.Diagnostics.HealthChecks.IHealthCheck> 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<string> 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<string, object> data = null) { throw null; }
|
||||
public System.Collections.Generic.IReadOnlyDictionary<string, object> 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<string, object> data = null) { throw null; }
|
||||
public static Microsoft.Extensions.Diagnostics.HealthChecks.HealthCheckResult Healthy(string description = null, System.Collections.Generic.IReadOnlyDictionary<string, object> data = null) { throw null; }
|
||||
public static Microsoft.Extensions.Diagnostics.HealthChecks.HealthCheckResult Unhealthy(string description = null, System.Exception exception = null, System.Collections.Generic.IReadOnlyDictionary<string, object> data = null) { throw null; }
|
||||
}
|
||||
public sealed partial class HealthReport
|
||||
{
|
||||
public HealthReport(System.Collections.Generic.IReadOnlyDictionary<string, Microsoft.Extensions.Diagnostics.HealthChecks.HealthReportEntry> entries, System.TimeSpan totalDuration) { }
|
||||
public System.Collections.Generic.IReadOnlyDictionary<string, Microsoft.Extensions.Diagnostics.HealthChecks.HealthReportEntry> 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<string, object> data) { throw null; }
|
||||
public System.Collections.Generic.IReadOnlyDictionary<string, object> 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<Microsoft.Extensions.Diagnostics.HealthChecks.HealthCheckResult> 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);
|
||||
}
|
||||
}
|
||||
|
|
@ -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<IServiceProvider, IHealthCheck> _factory;
|
||||
private string _name;
|
||||
private TimeSpan _timeout;
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new <see cref="T:Microsoft.Extensions.Diagnostics.HealthChecks.HealthCheckRegistration" /> for an existing <see cref="T:Microsoft.Extensions.Diagnostics.HealthChecks.IHealthCheck" /> instance.
|
||||
/// </summary>
|
||||
/// <param name="name">The health check name.</param>
|
||||
/// <param name="instance">The <see cref="T:Microsoft.Extensions.Diagnostics.HealthChecks.IHealthCheck" /> instance.</param>
|
||||
/// <param name="failureStatus">
|
||||
/// The <see cref="T:Microsoft.Extensions.Diagnostics.HealthChecks.HealthStatus" /> that should be reported upon failure of the health check. If the provided value
|
||||
/// is <c>null</c>, then <see cref="F:Microsoft.Extensions.Diagnostics.HealthChecks.HealthStatus.Unhealthy" /> will be reported.
|
||||
/// </param>
|
||||
/// <param name="tags">A list of tags that can be used for filtering health checks.</param>
|
||||
public HealthCheckRegistration(string name, IHealthCheck instance, HealthStatus? failureStatus, IEnumerable<string> tags)
|
||||
: this(name, instance, failureStatus, tags, default)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new <see cref="HealthCheckRegistration"/> for an existing <see cref="IHealthCheck"/> instance.
|
||||
|
|
@ -35,7 +51,8 @@ namespace Microsoft.Extensions.Diagnostics.HealthChecks
|
|||
/// is <c>null</c>, then <see cref="HealthStatus.Unhealthy"/> will be reported.
|
||||
/// </param>
|
||||
/// <param name="tags">A list of tags that can be used for filtering health checks.</param>
|
||||
public HealthCheckRegistration(string name, IHealthCheck instance, HealthStatus? failureStatus, IEnumerable<string> tags)
|
||||
/// <param name="timeout">An optional <see cref="TimeSpan"/> representing the timeout of the check.</param>
|
||||
public HealthCheckRegistration(string name, IHealthCheck instance, HealthStatus? failureStatus, IEnumerable<string> 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<string>(tags ?? Array.Empty<string>(), StringComparer.OrdinalIgnoreCase);
|
||||
Factory = (_) => instance;
|
||||
Timeout = timeout ?? System.Threading.Timeout.InfiniteTimeSpan;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
|
@ -68,6 +91,27 @@ namespace Microsoft.Extensions.Diagnostics.HealthChecks
|
|||
Func<IServiceProvider, IHealthCheck> factory,
|
||||
HealthStatus? failureStatus,
|
||||
IEnumerable<string> tags)
|
||||
: this(name, factory, failureStatus, tags, default)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new <see cref="HealthCheckRegistration"/> for an existing <see cref="IHealthCheck"/> instance.
|
||||
/// </summary>
|
||||
/// <param name="name">The health check name.</param>
|
||||
/// <param name="factory">A delegate used to create the <see cref="IHealthCheck"/> instance.</param>
|
||||
/// <param name="failureStatus">
|
||||
/// The <see cref="HealthStatus"/> that should be reported when the health check reports a failure. If the provided value
|
||||
/// is <c>null</c>, then <see cref="HealthStatus.Unhealthy"/> will be reported.
|
||||
/// </param>
|
||||
/// <param name="tags">A list of tags that can be used for filtering health checks.</param>
|
||||
/// <param name="timeout">An optional <see cref="TimeSpan"/> representing the timeout of the check.</param>
|
||||
public HealthCheckRegistration(
|
||||
string name,
|
||||
Func<IServiceProvider, IHealthCheck> factory,
|
||||
HealthStatus? failureStatus,
|
||||
IEnumerable<string> 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<string>(tags ?? Array.Empty<string>(), StringComparer.OrdinalIgnoreCase);
|
||||
Factory = factory;
|
||||
Timeout = timeout ?? System.Threading.Timeout.InfiniteTimeSpan;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
|
@ -107,6 +157,23 @@ namespace Microsoft.Extensions.Diagnostics.HealthChecks
|
|||
/// </summary>
|
||||
public HealthStatus FailureStatus { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the timeout used for the test.
|
||||
/// </summary>
|
||||
public TimeSpan Timeout
|
||||
{
|
||||
get => _timeout;
|
||||
set
|
||||
{
|
||||
if (value <= TimeSpan.Zero && value != System.Threading.Timeout.InfiniteTimeSpan)
|
||||
{
|
||||
throw new ArgumentOutOfRangeException(nameof(value));
|
||||
}
|
||||
|
||||
_timeout = value;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the health check name.
|
||||
/// </summary>
|
||||
|
|
|
|||
|
|
@ -70,7 +70,7 @@ namespace Microsoft.Extensions.Diagnostics.HealthChecks
|
|||
/// <returns>A <see cref="HealthCheckResult"/> representing a degraged component.</returns>
|
||||
public static HealthCheckResult Degraded(string description = null, Exception exception = null, IReadOnlyDictionary<string, object> data = null)
|
||||
{
|
||||
return new HealthCheckResult(status: HealthStatus.Degraded, description, exception: null, data);
|
||||
return new HealthCheckResult(status: HealthStatus.Degraded, description, exception: exception, data);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
|
|
|||
|
|
@ -11,6 +11,7 @@ Microsoft.Extensions.Diagnostics.HealthChecks.IHealthCheck
|
|||
<NoWarn>$(NoWarn);CS1591</NoWarn>
|
||||
<GenerateDocumentationFile>true</GenerateDocumentationFile>
|
||||
<PackageTags>diagnostics;healthchecks</PackageTags>
|
||||
<IsShipping>true</IsShipping>
|
||||
</PropertyGroup>
|
||||
|
||||
</Project>
|
||||
|
|
|
|||
|
|
@ -1,5 +0,0 @@
|
|||
{
|
||||
"AssemblyIdentity": "Microsoft.Extensions.Diagnostics.HealthChecks.Abstractions, Version=2.1.1.0, Culture=neutral, PublicKeyToken=adb9793829ddae60",
|
||||
"Types": [
|
||||
]
|
||||
}
|
||||
|
|
@ -1,7 +0,0 @@
|
|||
<Project>
|
||||
<Import Project="$([MSBuild]::GetDirectoryNameOfFileAbove($(MSBuildThisFileDirectory)..\, Directory.Build.props))\Directory.Build.props" />
|
||||
|
||||
<PropertyGroup>
|
||||
<IsProductComponent>true</IsProductComponent>
|
||||
</PropertyGroup>
|
||||
</Project>
|
||||
|
|
@ -0,0 +1,12 @@
|
|||
<!-- This file is automatically generated. -->
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<PropertyGroup>
|
||||
<TargetFrameworks>netstandard2.0</TargetFrameworks>
|
||||
</PropertyGroup>
|
||||
<ItemGroup Condition="'$(TargetFramework)' == 'netstandard2.0'">
|
||||
<Compile Include="Microsoft.Extensions.Diagnostics.HealthChecks.netstandard2.0.cs" />
|
||||
<Reference Include="Microsoft.Extensions.Diagnostics.HealthChecks.Abstractions" />
|
||||
<Reference Include="Microsoft.Extensions.Hosting.Abstractions" />
|
||||
<Reference Include="Microsoft.Extensions.Options" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
|
|
@ -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<string> 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<string> tags = null, System.TimeSpan? timeout = default(System.TimeSpan?)) { throw null; }
|
||||
public static Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder AddCheck<T>(this Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder builder, string name, Microsoft.Extensions.Diagnostics.HealthChecks.HealthStatus? failureStatus, System.Collections.Generic.IEnumerable<string> tags) where T : class, Microsoft.Extensions.Diagnostics.HealthChecks.IHealthCheck { throw null; }
|
||||
public static Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder AddCheck<T>(this Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder builder, string name, Microsoft.Extensions.Diagnostics.HealthChecks.HealthStatus? failureStatus = default(Microsoft.Extensions.Diagnostics.HealthChecks.HealthStatus?), System.Collections.Generic.IEnumerable<string> 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<T>(this Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder builder, string name, Microsoft.Extensions.Diagnostics.HealthChecks.HealthStatus? failureStatus, System.Collections.Generic.IEnumerable<string> tags, params object[] args) where T : class, Microsoft.Extensions.Diagnostics.HealthChecks.IHealthCheck { throw null; }
|
||||
public static Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder AddTypeActivatedCheck<T>(this Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder builder, string name, Microsoft.Extensions.Diagnostics.HealthChecks.HealthStatus? failureStatus, System.Collections.Generic.IEnumerable<string> tags, System.TimeSpan timeout, params object[] args) where T : class, Microsoft.Extensions.Diagnostics.HealthChecks.IHealthCheck { throw null; }
|
||||
public static Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder AddTypeActivatedCheck<T>(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<T>(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<System.Threading.CancellationToken, System.Threading.Tasks.Task<Microsoft.Extensions.Diagnostics.HealthChecks.HealthCheckResult>> check, System.Collections.Generic.IEnumerable<string> tags) { throw null; }
|
||||
public static Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder AddAsyncCheck(this Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder builder, string name, System.Func<System.Threading.CancellationToken, System.Threading.Tasks.Task<Microsoft.Extensions.Diagnostics.HealthChecks.HealthCheckResult>> check, System.Collections.Generic.IEnumerable<string> 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<System.Threading.Tasks.Task<Microsoft.Extensions.Diagnostics.HealthChecks.HealthCheckResult>> check, System.Collections.Generic.IEnumerable<string> tags) { throw null; }
|
||||
public static Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder AddAsyncCheck(this Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder builder, string name, System.Func<System.Threading.Tasks.Task<Microsoft.Extensions.Diagnostics.HealthChecks.HealthCheckResult>> check, System.Collections.Generic.IEnumerable<string> 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<Microsoft.Extensions.Diagnostics.HealthChecks.HealthCheckResult> check, System.Collections.Generic.IEnumerable<string> tags) { throw null; }
|
||||
public static Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder AddCheck(this Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder builder, string name, System.Func<Microsoft.Extensions.Diagnostics.HealthChecks.HealthCheckResult> check, System.Collections.Generic.IEnumerable<string> 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<System.Threading.CancellationToken, Microsoft.Extensions.Diagnostics.HealthChecks.HealthCheckResult> check, System.Collections.Generic.IEnumerable<string> tags) { throw null; }
|
||||
public static Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder AddCheck(this Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder builder, string name, System.Func<System.Threading.CancellationToken, Microsoft.Extensions.Diagnostics.HealthChecks.HealthCheckResult> check, System.Collections.Generic.IEnumerable<string> 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<Microsoft.Extensions.Diagnostics.HealthChecks.HealthCheckRegistration, bool> 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<Microsoft.Extensions.Diagnostics.HealthChecks.HealthReport> CheckHealthAsync(System.Func<Microsoft.Extensions.Diagnostics.HealthChecks.HealthCheckRegistration, bool> predicate, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken));
|
||||
public System.Threading.Tasks.Task<Microsoft.Extensions.Diagnostics.HealthChecks.HealthReport> CheckHealthAsync(System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) { throw null; }
|
||||
}
|
||||
public sealed partial class HealthCheckServiceOptions
|
||||
{
|
||||
public HealthCheckServiceOptions() { }
|
||||
public System.Collections.Generic.ICollection<Microsoft.Extensions.Diagnostics.HealthChecks.HealthCheckRegistration> Registrations { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } }
|
||||
}
|
||||
}
|
||||
|
|
@ -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<HealthReportEntry>[registrations.Count];
|
||||
var index = 0;
|
||||
using (var scope = _scopeFactory.CreateScope())
|
||||
{
|
||||
var context = new HealthCheckContext();
|
||||
var entries = new Dictionary<string, HealthReportEntry>(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<string, HealthReportEntry>(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<HealthReportEntry> 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;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -26,7 +26,7 @@ namespace Microsoft.Extensions.DependencyInjection
|
|||
public static IHealthChecksBuilder AddHealthChecks(this IServiceCollection services)
|
||||
{
|
||||
services.TryAddSingleton<HealthCheckService, DefaultHealthCheckService>();
|
||||
services.TryAddSingleton<IHostedService, HealthCheckPublisherHostedService>();
|
||||
services.TryAddEnumerable(ServiceDescriptor.Singleton<IHostedService, HealthCheckPublisherHostedService>());
|
||||
return new HealthChecksBuilder(services);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -24,12 +24,37 @@ namespace Microsoft.Extensions.DependencyInjection
|
|||
/// </param>
|
||||
/// <param name="tags">A list of tags that can be used to filter health checks.</param>
|
||||
/// <returns>The <see cref="IHealthChecksBuilder"/>.</returns>
|
||||
// 2.0 BACKCOMPAT OVERLOAD -- DO NOT TOUCH
|
||||
public static IHealthChecksBuilder AddCheck(
|
||||
this IHealthChecksBuilder builder,
|
||||
string name,
|
||||
IHealthCheck instance,
|
||||
HealthStatus? failureStatus,
|
||||
IEnumerable<string> tags)
|
||||
{
|
||||
return AddCheck(builder, name, instance, failureStatus, tags, default);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds a new health check with the specified name and implementation.
|
||||
/// </summary>
|
||||
/// <param name="builder">The <see cref="IHealthChecksBuilder"/>.</param>
|
||||
/// <param name="name">The name of the health check.</param>
|
||||
/// <param name="instance">An <see cref="IHealthCheck"/> instance.</param>
|
||||
/// <param name="failureStatus">
|
||||
/// The <see cref="HealthStatus"/> that should be reported when the health check reports a failure. If the provided value
|
||||
/// is <c>null</c>, then <see cref="HealthStatus.Unhealthy"/> will be reported.
|
||||
/// </param>
|
||||
/// <param name="tags">A list of tags that can be used to filter health checks.</param>
|
||||
/// <param name="timeout">An optional <see cref="TimeSpan"/> representing the timeout of the check.</param>
|
||||
/// <returns>The <see cref="IHealthChecksBuilder"/>.</returns>
|
||||
public static IHealthChecksBuilder AddCheck(
|
||||
this IHealthChecksBuilder builder,
|
||||
string name,
|
||||
IHealthCheck instance,
|
||||
HealthStatus? failureStatus = null,
|
||||
IEnumerable<string> tags = null)
|
||||
IEnumerable<string> 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));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
|
@ -63,15 +88,45 @@ namespace Microsoft.Extensions.DependencyInjection
|
|||
/// <returns>The <see cref="IHealthChecksBuilder"/>.</returns>
|
||||
/// <remarks>
|
||||
/// This method will use <see cref="ActivatorUtilities.GetServiceOrCreateInstance{T}(IServiceProvider)"/> to create the health check
|
||||
/// instance when needed. If a service of type <typeparamref name="T"/> is registred in the dependency injection container
|
||||
/// with any liftime it will be used. Otherwise an instance of type <typeparamref name="T"/> will be constructed with
|
||||
/// instance when needed. If a service of type <typeparamref name="T"/> is registered in the dependency injection container
|
||||
/// with any lifetime it will be used. Otherwise an instance of type <typeparamref name="T"/> will be constructed with
|
||||
/// access to services from the dependency injection container.
|
||||
/// </remarks>
|
||||
// 2.0 BACKCOMPAT OVERLOAD -- DO NOT TOUCH
|
||||
public static IHealthChecksBuilder AddCheck<T>(
|
||||
this IHealthChecksBuilder builder,
|
||||
string name,
|
||||
HealthStatus? failureStatus,
|
||||
IEnumerable<string> tags) where T : class, IHealthCheck
|
||||
{
|
||||
return AddCheck<T>(builder, name, failureStatus, tags, default);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds a new health check with the specified name and implementation.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The health check implementation type.</typeparam>
|
||||
/// <param name="builder">The <see cref="IHealthChecksBuilder"/>.</param>
|
||||
/// <param name="name">The name of the health check.</param>
|
||||
/// <param name="failureStatus">
|
||||
/// The <see cref="HealthStatus"/> that should be reported when the health check reports a failure. If the provided value
|
||||
/// is <c>null</c>, then <see cref="HealthStatus.Unhealthy"/> will be reported.
|
||||
/// </param>
|
||||
/// <param name="tags">A list of tags that can be used to filter health checks.</param>
|
||||
/// <param name="timeout">An optional <see cref="TimeSpan"/> representing the timeout of the check.</param>
|
||||
/// <returns>The <see cref="IHealthChecksBuilder"/>.</returns>
|
||||
/// <remarks>
|
||||
/// This method will use <see cref="ActivatorUtilities.GetServiceOrCreateInstance{T}(IServiceProvider)"/> to create the health check
|
||||
/// instance when needed. If a service of type <typeparamref name="T"/> is registered in the dependency injection container
|
||||
/// with any lifetime it will be used. Otherwise an instance of type <typeparamref name="T"/> will be constructed with
|
||||
/// access to services from the dependency injection container.
|
||||
/// </remarks>
|
||||
public static IHealthChecksBuilder AddCheck<T>(
|
||||
this IHealthChecksBuilder builder,
|
||||
string name,
|
||||
HealthStatus? failureStatus = null,
|
||||
IEnumerable<string> tags = null) where T : class, IHealthCheck
|
||||
IEnumerable<string> 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<T>(s), failureStatus, tags));
|
||||
return builder.Add(new HealthCheckRegistration(name, s => ActivatorUtilities.GetServiceOrCreateInstance<T>(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<T>(builder, name, failureStatus: null, tags: null);
|
||||
return AddTypeActivatedCheck<T>(builder, name, failureStatus: null, tags: null, args);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
|
@ -148,7 +203,7 @@ namespace Microsoft.Extensions.DependencyInjection
|
|||
throw new ArgumentNullException(nameof(name));
|
||||
}
|
||||
|
||||
return AddTypeActivatedCheck<T>(builder, name, failureStatus, tags: null);
|
||||
return AddTypeActivatedCheck<T>(builder, name, failureStatus, tags: null, args);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
|
@ -187,5 +242,44 @@ namespace Microsoft.Extensions.DependencyInjection
|
|||
|
||||
return builder.Add(new HealthCheckRegistration(name, s => ActivatorUtilities.CreateInstance<T>(s, args), failureStatus, tags));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds a new type activated health check with the specified name and implementation.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The health check implementation type.</typeparam>
|
||||
/// <param name="builder">The <see cref="IHealthChecksBuilder"/>.</param>
|
||||
/// <param name="name">The name of the health check.</param>
|
||||
/// <param name="failureStatus">
|
||||
/// The <see cref="HealthStatus"/> that should be reported when the health check reports a failure. If the provided value
|
||||
/// is <c>null</c>, then <see cref="HealthStatus.Unhealthy"/> will be reported.
|
||||
/// </param>
|
||||
/// <param name="tags">A list of tags that can be used to filter health checks.</param>
|
||||
/// <param name="args">Additional arguments to provide to the constructor.</param>
|
||||
/// <param name="timeout">A <see cref="TimeSpan"/> representing the timeout of the check.</param>
|
||||
/// <returns>The <see cref="IHealthChecksBuilder"/>.</returns>
|
||||
/// <remarks>
|
||||
/// This method will use <see cref="ActivatorUtilities.CreateInstance{T}(IServiceProvider, object[])"/> to create the health check
|
||||
/// instance when needed. Additional arguments can be provided to the constructor via <paramref name="args"/>.
|
||||
/// </remarks>
|
||||
public static IHealthChecksBuilder AddTypeActivatedCheck<T>(
|
||||
this IHealthChecksBuilder builder,
|
||||
string name,
|
||||
HealthStatus? failureStatus,
|
||||
IEnumerable<string> 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<T>(s, args), failureStatus, tags, timeout));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
|||
/// <param name="tags">A list of tags that can be used to filter health checks.</param>
|
||||
/// <param name="check">A delegate that provides the health check implementation.</param>
|
||||
/// <returns>The <see cref="IHealthChecksBuilder"/>.</returns>
|
||||
// 2.0 BACKCOMPAT OVERLOAD -- DO NOT TOUCH
|
||||
public static IHealthChecksBuilder AddCheck(
|
||||
this IHealthChecksBuilder builder,
|
||||
string name,
|
||||
Func<HealthCheckResult> check,
|
||||
IEnumerable<string> tags = null)
|
||||
IEnumerable<string> tags)
|
||||
{
|
||||
return AddCheck(builder, name, check, tags, default);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds a new health check with the specified name and implementation.
|
||||
/// </summary>
|
||||
/// <param name="builder">The <see cref="IHealthChecksBuilder"/>.</param>
|
||||
/// <param name="name">The name of the health check.</param>
|
||||
/// <param name="tags">A list of tags that can be used to filter health checks.</param>
|
||||
/// <param name="check">A delegate that provides the health check implementation.</param>
|
||||
/// <param name="timeout">An optional <see cref="TimeSpan"/> representing the timeout of the check.</param>
|
||||
/// <returns>The <see cref="IHealthChecksBuilder"/>.</returns>
|
||||
public static IHealthChecksBuilder AddCheck(
|
||||
this IHealthChecksBuilder builder,
|
||||
string name,
|
||||
Func<HealthCheckResult> check,
|
||||
IEnumerable<string> 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));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
|
@ -55,11 +75,31 @@ namespace Microsoft.Extensions.DependencyInjection
|
|||
/// <param name="tags">A list of tags that can be used to filter health checks.</param>
|
||||
/// <param name="check">A delegate that provides the health check implementation.</param>
|
||||
/// <returns>The <see cref="IHealthChecksBuilder"/>.</returns>
|
||||
// 2.0 BACKCOMPAT OVERLOAD -- DO NOT TOUCH
|
||||
public static IHealthChecksBuilder AddCheck(
|
||||
this IHealthChecksBuilder builder,
|
||||
string name,
|
||||
Func<CancellationToken, HealthCheckResult> check,
|
||||
IEnumerable<string> tags = null)
|
||||
IEnumerable<string> tags)
|
||||
{
|
||||
return AddCheck(builder, name, check, tags, default);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds a new health check with the specified name and implementation.
|
||||
/// </summary>
|
||||
/// <param name="builder">The <see cref="IHealthChecksBuilder"/>.</param>
|
||||
/// <param name="name">The name of the health check.</param>
|
||||
/// <param name="tags">A list of tags that can be used to filter health checks.</param>
|
||||
/// <param name="check">A delegate that provides the health check implementation.</param>
|
||||
/// <param name="timeout">An optional <see cref="TimeSpan"/> representing the timeout of the check.</param>
|
||||
/// <returns>The <see cref="IHealthChecksBuilder"/>.</returns>
|
||||
public static IHealthChecksBuilder AddCheck(
|
||||
this IHealthChecksBuilder builder,
|
||||
string name,
|
||||
Func<CancellationToken, HealthCheckResult> check,
|
||||
IEnumerable<string> 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));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
|
@ -88,11 +128,31 @@ namespace Microsoft.Extensions.DependencyInjection
|
|||
/// <param name="tags">A list of tags that can be used to filter health checks.</param>
|
||||
/// <param name="check">A delegate that provides the health check implementation.</param>
|
||||
/// <returns>The <see cref="IHealthChecksBuilder"/>.</returns>
|
||||
// 2.0 BACKCOMPAT OVERLOAD -- DO NOT TOUCH
|
||||
public static IHealthChecksBuilder AddAsyncCheck(
|
||||
this IHealthChecksBuilder builder,
|
||||
string name,
|
||||
Func<Task<HealthCheckResult>> check,
|
||||
IEnumerable<string> tags = null)
|
||||
IEnumerable<string> tags)
|
||||
{
|
||||
return AddAsyncCheck(builder, name, check, tags, default);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds a new health check with the specified name and implementation.
|
||||
/// </summary>
|
||||
/// <param name="builder">The <see cref="IHealthChecksBuilder"/>.</param>
|
||||
/// <param name="name">The name of the health check.</param>
|
||||
/// <param name="tags">A list of tags that can be used to filter health checks.</param>
|
||||
/// <param name="check">A delegate that provides the health check implementation.</param>
|
||||
/// <param name="timeout">An optional <see cref="TimeSpan"/> representing the timeout of the check.</param>
|
||||
/// <returns>The <see cref="IHealthChecksBuilder"/>.</returns>
|
||||
public static IHealthChecksBuilder AddAsyncCheck(
|
||||
this IHealthChecksBuilder builder,
|
||||
string name,
|
||||
Func<Task<HealthCheckResult>> check,
|
||||
IEnumerable<string> 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));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
|
@ -121,11 +181,31 @@ namespace Microsoft.Extensions.DependencyInjection
|
|||
/// <param name="tags">A list of tags that can be used to filter health checks.</param>
|
||||
/// <param name="check">A delegate that provides the health check implementation.</param>
|
||||
/// <returns>The <see cref="IHealthChecksBuilder"/>.</returns>
|
||||
// 2.0 BACKCOMPAT OVERLOAD -- DO NOT TOUCH
|
||||
public static IHealthChecksBuilder AddAsyncCheck(
|
||||
this IHealthChecksBuilder builder,
|
||||
string name,
|
||||
Func<CancellationToken, Task<HealthCheckResult>> check,
|
||||
IEnumerable<string> tags = null)
|
||||
IEnumerable<string> tags)
|
||||
{
|
||||
return AddAsyncCheck(builder, name, check, tags, default);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds a new health check with the specified name and implementation.
|
||||
/// </summary>
|
||||
/// <param name="builder">The <see cref="IHealthChecksBuilder"/>.</param>
|
||||
/// <param name="name">The name of the health check.</param>
|
||||
/// <param name="tags">A list of tags that can be used to filter health checks.</param>
|
||||
/// <param name="check">A delegate that provides the health check implementation.</param>
|
||||
/// <param name="timeout">An optional <see cref="TimeSpan"/> representing the timeout of the check.</param>
|
||||
/// <returns>The <see cref="IHealthChecksBuilder"/>.</returns>
|
||||
public static IHealthChecksBuilder AddAsyncCheck(
|
||||
this IHealthChecksBuilder builder,
|
||||
string name,
|
||||
Func<CancellationToken, Task<HealthCheckResult>> check,
|
||||
IEnumerable<string> 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));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -10,8 +10,13 @@ Microsoft.Extensions.Diagnostics.HealthChecks.IHealthChecksBuilder
|
|||
<NoWarn>$(NoWarn);CS1591</NoWarn>
|
||||
<GenerateDocumentationFile>true</GenerateDocumentationFile>
|
||||
<PackageTags>diagnostics;healthchecks</PackageTags>
|
||||
<IsShipping>true</IsShipping>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<InternalsVisibleTo Include="Microsoft.Extensions.Diagnostics.HealthChecks.Tests" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Compile Include="$(SharedSourceRoot)NonCapturingTimer\**\*.cs" />
|
||||
<Compile Include="$(SharedSourceRoot)ValueStopwatch\**\*.cs" />
|
||||
|
|
|
|||
|
|
@ -1,3 +0,0 @@
|
|||
using System.Runtime.CompilerServices;
|
||||
|
||||
[assembly: InternalsVisibleTo("Microsoft.Extensions.Diagnostics.HealthChecks.Tests, PublicKey=0024000004800000940000000602000000240000525341310004000001000100f33a29044fa9d740c9b3213a93e57c84b472c84e0b8a0e1ae48e67a9f8f6de9d5f7f3d52ac23e48ac51801f1dc950abe901da34d2a9e3baadb141a17c77ef3c565dd5ee5054b91cf63bb3c6ab83f72ab3aafe93d0fc3c2348b764fafb0b1c0733de51459aeab46580384bf9d74c4e28164b7cde247f891ba07891c9d872ad2bb")]
|
||||
|
|
@ -1,5 +0,0 @@
|
|||
{
|
||||
"AssemblyIdentity": "Microsoft.Extensions.Diagnostics.HealthChecks, Version=2.1.1.0, Culture=neutral, PublicKeyToken=adb9793829ddae60",
|
||||
"Types": [
|
||||
]
|
||||
}
|
||||
|
|
@ -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<object>(TaskCreationOptions.RunContinuationsAsynchronously);
|
||||
var input2 = new TaskCompletionSource<object>(TaskCreationOptions.RunContinuationsAsynchronously);
|
||||
var output1 = new TaskCompletionSource<object>(TaskCreationOptions.RunContinuationsAsynchronously);
|
||||
var output2 = new TaskCompletionSource<object>(TaskCreationOptions.RunContinuationsAsynchronously);
|
||||
|
||||
var service = CreateHealthChecksService(b =>
|
||||
{
|
||||
b.AddAsyncCheck("test1",
|
||||
async () =>
|
||||
{
|
||||
output1.SetResult(null);
|
||||
await input1.Task;
|
||||
return HealthCheckResult.Healthy();
|
||||
});
|
||||
b.AddAsyncCheck("test2",
|
||||
async () =>
|
||||
{
|
||||
output2.SetResult(null);
|
||||
await input2.Task;
|
||||
return HealthCheckResult.Healthy();
|
||||
});
|
||||
});
|
||||
|
||||
// Act
|
||||
var checkHealthTask = service.CheckHealthAsync();
|
||||
await Task.WhenAll(output1.Task, output2.Task).TimeoutAfter(TimeSpan.FromSeconds(10));
|
||||
input1.SetResult(null);
|
||||
input2.SetResult(null);
|
||||
await checkHealthTask;
|
||||
|
||||
// Assert
|
||||
Assert.Collection(checkHealthTask.Result.Entries,
|
||||
entry =>
|
||||
{
|
||||
Assert.Equal("test1", entry.Key);
|
||||
Assert.Equal(HealthStatus.Healthy, entry.Value.Status);
|
||||
},
|
||||
entry =>
|
||||
{
|
||||
Assert.Equal("test2", entry.Key);
|
||||
Assert.Equal(HealthStatus.Healthy, entry.Value.Status);
|
||||
});
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task CheckHealthAsync_TimeoutReturnsUnhealthy()
|
||||
{
|
||||
// Arrange
|
||||
var service = CreateHealthChecksService(b =>
|
||||
{
|
||||
b.AddAsyncCheck("timeout", async (ct) =>
|
||||
{
|
||||
await Task.Delay(2000, ct);
|
||||
return HealthCheckResult.Healthy();
|
||||
}, timeout: TimeSpan.FromMilliseconds(100));
|
||||
});
|
||||
|
||||
// Act
|
||||
var results = await service.CheckHealthAsync();
|
||||
|
||||
// Assert
|
||||
Assert.Collection(
|
||||
results.Entries,
|
||||
actual =>
|
||||
{
|
||||
Assert.Equal("timeout", actual.Key);
|
||||
Assert.Equal(HealthStatus.Unhealthy, actual.Value.Status);
|
||||
});
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CheckHealthAsync_WorksInSingleThreadedSyncContext()
|
||||
{
|
||||
// Arrange
|
||||
var service = CreateHealthChecksService(b =>
|
||||
{
|
||||
b.AddAsyncCheck("test", async () =>
|
||||
{
|
||||
await Task.Delay(1).ConfigureAwait(false);
|
||||
return HealthCheckResult.Healthy();
|
||||
});
|
||||
});
|
||||
|
||||
var hangs = true;
|
||||
|
||||
// Act
|
||||
using (var cts = new CancellationTokenSource(TimeSpan.FromSeconds(3)))
|
||||
{
|
||||
var token = cts.Token;
|
||||
token.Register(() => throw new OperationCanceledException(token));
|
||||
|
||||
SingleThreadedSynchronizationContext.Run(() =>
|
||||
{
|
||||
// Act
|
||||
service.CheckHealthAsync(token).GetAwaiter().GetResult();
|
||||
hangs = false;
|
||||
});
|
||||
}
|
||||
|
||||
// Assert
|
||||
Assert.False(hangs);
|
||||
}
|
||||
|
||||
private static DefaultHealthCheckService CreateHealthChecksService(Action<IHealthChecksBuilder> configure)
|
||||
{
|
||||
var services = new ServiceCollection();
|
||||
|
|
|
|||
|
|
@ -2,6 +2,8 @@
|
|||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.Extensions.Diagnostics.HealthChecks;
|
||||
using Microsoft.Extensions.Hosting;
|
||||
using Xunit;
|
||||
|
|
@ -39,5 +41,56 @@ namespace Microsoft.Extensions.DependencyInjection
|
|||
Assert.Null(actual.ImplementationFactory);
|
||||
});
|
||||
}
|
||||
|
||||
[Fact] // see: https://github.com/aspnet/Extensions/issues/639
|
||||
public void AddHealthChecks_RegistersPublisherService_WhenOtherHostedServicesRegistered()
|
||||
{
|
||||
// Arrange
|
||||
var services = new ServiceCollection();
|
||||
|
||||
// Act
|
||||
services.AddSingleton<IHostedService, DummyHostedService>();
|
||||
services.AddHealthChecks();
|
||||
|
||||
// Assert
|
||||
Assert.Collection(services.OrderBy(s => s.ServiceType.FullName).ThenBy(s => s.ImplementationType.FullName),
|
||||
actual =>
|
||||
{
|
||||
Assert.Equal(ServiceLifetime.Singleton, actual.Lifetime);
|
||||
Assert.Equal(typeof(HealthCheckService), actual.ServiceType);
|
||||
Assert.Equal(typeof(DefaultHealthCheckService), actual.ImplementationType);
|
||||
Assert.Null(actual.ImplementationInstance);
|
||||
Assert.Null(actual.ImplementationFactory);
|
||||
},
|
||||
actual =>
|
||||
{
|
||||
Assert.Equal(ServiceLifetime.Singleton, actual.Lifetime);
|
||||
Assert.Equal(typeof(IHostedService), actual.ServiceType);
|
||||
Assert.Equal(typeof(DummyHostedService), actual.ImplementationType);
|
||||
Assert.Null(actual.ImplementationInstance);
|
||||
Assert.Null(actual.ImplementationFactory);
|
||||
},
|
||||
actual =>
|
||||
{
|
||||
Assert.Equal(ServiceLifetime.Singleton, actual.Lifetime);
|
||||
Assert.Equal(typeof(IHostedService), actual.ServiceType);
|
||||
Assert.Equal(typeof(HealthCheckPublisherHostedService), actual.ImplementationType);
|
||||
Assert.Null(actual.ImplementationInstance);
|
||||
Assert.Null(actual.ImplementationFactory);
|
||||
});
|
||||
}
|
||||
|
||||
private class DummyHostedService : IHostedService
|
||||
{
|
||||
public Task StartAsync(CancellationToken cancellationToken)
|
||||
{
|
||||
throw new System.NotImplementedException();
|
||||
}
|
||||
|
||||
public Task StopAsync(CancellationToken cancellationToken)
|
||||
{
|
||||
throw new System.NotImplementedException();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -211,8 +211,8 @@ namespace Microsoft.Extensions.Diagnostics.HealthChecks
|
|||
entry => { Assert.Equal(HealthCheckPublisherHostedService.EventIds.HealthCheckPublisherProcessingBegin, entry.EventId); },
|
||||
entry => { Assert.Equal(DefaultHealthCheckService.EventIds.HealthCheckProcessingBegin, entry.EventId); },
|
||||
entry => { Assert.Equal(DefaultHealthCheckService.EventIds.HealthCheckBegin, entry.EventId); },
|
||||
entry => { Assert.Equal(DefaultHealthCheckService.EventIds.HealthCheckEnd, entry.EventId); },
|
||||
entry => { Assert.Equal(DefaultHealthCheckService.EventIds.HealthCheckBegin, entry.EventId); },
|
||||
entry => { Assert.Contains(entry.EventId, new[] { DefaultHealthCheckService.EventIds.HealthCheckBegin, DefaultHealthCheckService.EventIds.HealthCheckEnd }); },
|
||||
entry => { Assert.Contains(entry.EventId, new[] { DefaultHealthCheckService.EventIds.HealthCheckBegin, DefaultHealthCheckService.EventIds.HealthCheckEnd }); },
|
||||
entry => { Assert.Equal(DefaultHealthCheckService.EventIds.HealthCheckEnd, entry.EventId); },
|
||||
entry => { Assert.Equal(DefaultHealthCheckService.EventIds.HealthCheckProcessingEnd, entry.EventId); },
|
||||
entry => { Assert.Equal(HealthCheckPublisherHostedService.EventIds.HealthCheckPublisherBegin, entry.EventId); },
|
||||
|
|
@ -321,8 +321,8 @@ namespace Microsoft.Extensions.Diagnostics.HealthChecks
|
|||
entry => { Assert.Equal(HealthCheckPublisherHostedService.EventIds.HealthCheckPublisherProcessingBegin, entry.EventId); },
|
||||
entry => { Assert.Equal(DefaultHealthCheckService.EventIds.HealthCheckProcessingBegin, entry.EventId); },
|
||||
entry => { Assert.Equal(DefaultHealthCheckService.EventIds.HealthCheckBegin, entry.EventId); },
|
||||
entry => { Assert.Equal(DefaultHealthCheckService.EventIds.HealthCheckEnd, entry.EventId); },
|
||||
entry => { Assert.Equal(DefaultHealthCheckService.EventIds.HealthCheckBegin, entry.EventId); },
|
||||
entry => { Assert.Contains(entry.EventId, new[] { DefaultHealthCheckService.EventIds.HealthCheckBegin, DefaultHealthCheckService.EventIds.HealthCheckEnd }); },
|
||||
entry => { Assert.Contains(entry.EventId, new[] { DefaultHealthCheckService.EventIds.HealthCheckBegin, DefaultHealthCheckService.EventIds.HealthCheckEnd }); },
|
||||
entry => { Assert.Equal(DefaultHealthCheckService.EventIds.HealthCheckEnd, entry.EventId); },
|
||||
entry => { Assert.Equal(DefaultHealthCheckService.EventIds.HealthCheckProcessingEnd, entry.EventId); },
|
||||
entry => { Assert.Equal(HealthCheckPublisherHostedService.EventIds.HealthCheckPublisherBegin, entry.EventId); },
|
||||
|
|
@ -399,8 +399,8 @@ namespace Microsoft.Extensions.Diagnostics.HealthChecks
|
|||
entry => { Assert.Equal(HealthCheckPublisherHostedService.EventIds.HealthCheckPublisherProcessingBegin, entry.EventId); },
|
||||
entry => { Assert.Equal(DefaultHealthCheckService.EventIds.HealthCheckProcessingBegin, entry.EventId); },
|
||||
entry => { Assert.Equal(DefaultHealthCheckService.EventIds.HealthCheckBegin, entry.EventId); },
|
||||
entry => { Assert.Equal(DefaultHealthCheckService.EventIds.HealthCheckEnd, entry.EventId); },
|
||||
entry => { Assert.Equal(DefaultHealthCheckService.EventIds.HealthCheckBegin, entry.EventId); },
|
||||
entry => { Assert.Contains(entry.EventId, new[] { DefaultHealthCheckService.EventIds.HealthCheckBegin, DefaultHealthCheckService.EventIds.HealthCheckEnd }); },
|
||||
entry => { Assert.Contains(entry.EventId, new[] { DefaultHealthCheckService.EventIds.HealthCheckBegin, DefaultHealthCheckService.EventIds.HealthCheckEnd }); },
|
||||
entry => { Assert.Equal(DefaultHealthCheckService.EventIds.HealthCheckEnd, entry.EventId); },
|
||||
entry => { Assert.Equal(DefaultHealthCheckService.EventIds.HealthCheckProcessingEnd, entry.EventId); },
|
||||
entry => { Assert.Equal(HealthCheckPublisherHostedService.EventIds.HealthCheckPublisherBegin, entry.EventId); },
|
||||
|
|
|
|||
|
|
@ -1,9 +1,9 @@
|
|||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<Import Project="$(RepositoryRoot)src\Logging\Logging.Testing\src\build\Microsoft.Extensions.Logging.Testing.props" />
|
||||
<Import Project="$(RepoRoot)src\Logging\Logging.Testing\src\build\Microsoft.Extensions.Logging.Testing.props" />
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFrameworks>netcoreapp2.2;net461</TargetFrameworks>
|
||||
<TargetFrameworks>netcoreapp3.0;net472</TargetFrameworks>
|
||||
<RootNamespace>Microsoft.Extensions.Diagnostics.HealthChecks</RootNamespace>
|
||||
</PropertyGroup>
|
||||
|
||||
|
|
@ -12,4 +12,8 @@
|
|||
<Reference Include="Microsoft.Extensions.Logging.Testing" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Compile Include="..\..\..\Shared\test\SingleThreadedSynchronizationContext.cs" Link="Shared\SingleThreadedSynchronizationContext.cs"/>
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
|
|
|||
|
|
@ -0,0 +1,12 @@
|
|||
<Project>
|
||||
<Import Project="$([MSBuild]::GetDirectoryNameOfFileAbove($(MSBuildThisFileDirectory), Directory.Build.props))\Directory.Build.props" />
|
||||
|
||||
<PropertyGroup>
|
||||
<PackageId>@dotnet/jsinterop</PackageId>
|
||||
<IsPackable>true</IsPackable>
|
||||
<IsTestProject>false</IsTestProject>
|
||||
<IsShipping>true</IsShipping>
|
||||
</PropertyGroup>
|
||||
|
||||
<Import Project="$([MSBuild]::GetDirectoryNameOfFileAbove($(MSBuildThisFileDirectory), Directory.Build.targets))\Directory.Build.targets" />
|
||||
</Project>
|
||||
|
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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"
|
||||
}
|
||||
}
|
||||
|
|
@ -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<any> } = {};
|
||||
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<T>(assemblyName: string, methodIdentifier: string, ...args: any[]): T {
|
||||
return invokePossibleInstanceMethod<T>(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<T>(assemblyName: string, methodIdentifier: string, ...args: any[]): Promise<T> {
|
||||
return invokePossibleInstanceMethodAsync(assemblyName, methodIdentifier, null, args);
|
||||
}
|
||||
|
||||
function invokePossibleInstanceMethod<T>(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<T>(assemblyName: string | null, methodIdentifier: string, dotNetObjectId: number | null, args: any[]): Promise<T> {
|
||||
const asyncCallId = nextAsyncCallId++;
|
||||
const resultPromise = new Promise<T>((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<T> {
|
||||
resolve: (value?: T | PromiseLike<T>) => 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<any>(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<T>(methodIdentifier: string, ...args: any[]): T {
|
||||
return invokePossibleInstanceMethod<T>(null, methodIdentifier, this._id, args);
|
||||
}
|
||||
|
||||
public invokeMethodAsync<T>(methodIdentifier: string, ...args: any[]): Promise<T> {
|
||||
return invokePossibleInstanceMethodAsync<T>(null, methodIdentifier, this._id, args);
|
||||
}
|
||||
|
||||
public dispose() {
|
||||
const promise = invokeMethodAsync<any>(
|
||||
'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;
|
||||
}
|
||||
}
|
||||
|
|
@ -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/**"
|
||||
]
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,10 @@
|
|||
<!-- This file is automatically generated. -->
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<PropertyGroup>
|
||||
<TargetFrameworks>netstandard2.0</TargetFrameworks>
|
||||
</PropertyGroup>
|
||||
<ItemGroup Condition="'$(TargetFramework)' == 'netstandard2.0'">
|
||||
<Compile Include="Microsoft.JSInterop.netstandard2.0.cs" />
|
||||
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
|
|
@ -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<T>(string identifier, params object[] args);
|
||||
}
|
||||
public partial interface IJSRuntime
|
||||
{
|
||||
System.Threading.Tasks.Task<T> InvokeAsync<T>(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<T>(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<T>(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<T> InvokeAsync<T>(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() { }
|
||||
}
|
||||
}
|
||||
|
|
@ -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
|
||||
{
|
||||
/// <summary>
|
||||
/// Provides methods that receive incoming calls from JS to .NET.
|
||||
/// </summary>
|
||||
public static class DotNetDispatcher
|
||||
{
|
||||
private static ConcurrentDictionary<string, IReadOnlyDictionary<string, (MethodInfo, Type[])>> _cachedMethodsByAssembly
|
||||
= new ConcurrentDictionary<string, IReadOnlyDictionary<string, (MethodInfo, Type[])>>();
|
||||
|
||||
/// <summary>
|
||||
/// Receives a call from JS to .NET, locating and invoking the specified method.
|
||||
/// </summary>
|
||||
/// <param name="assemblyName">The assembly containing the method to be invoked.</param>
|
||||
/// <param name="methodIdentifier">The identifier of the method to be invoked. The method must be annotated with a <see cref="JSInvokableAttribute"/> matching this identifier string.</param>
|
||||
/// <param name="dotNetObjectId">For instance method calls, identifies the target object.</param>
|
||||
/// <param name="argsJson">A JSON representation of the parameters.</param>
|
||||
/// <returns>A JSON representation of the return value, or null.</returns>
|
||||
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);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Receives a call from JS to .NET, locating and invoking the specified method asynchronously.
|
||||
/// </summary>
|
||||
/// <param name="callId">A value identifying the asynchronous call that should be passed back with the result, or null if no result notification is required.</param>
|
||||
/// <param name="assemblyName">The assembly containing the method to be invoked.</param>
|
||||
/// <param name="methodIdentifier">The identifier of the method to be invoked. The method must be annotated with a <see cref="JSInvokableAttribute"/> matching this identifier string.</param>
|
||||
/// <param name="dotNetObjectId">For instance method calls, identifies the target object.</param>
|
||||
/// <param name="argsJson">A JSON representation of the parameters.</param>
|
||||
/// <returns>A JSON representation of the return value, or null.</returns>
|
||||
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<SimpleJson.JsonArray>(argsJson).ToArray<object>();
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Receives notification that a call from .NET to JS has finished, marking the
|
||||
/// associated <see cref="Task"/> as completed.
|
||||
/// </summary>
|
||||
/// <param name="asyncHandle">The identifier for the function invocation.</param>
|
||||
/// <param name="succeeded">A flag to indicate whether the invocation succeeded.</param>
|
||||
/// <param name="result">If <paramref name="succeeded"/> is <c>true</c>, specifies the invocation result. If <paramref name="succeeded"/> is <c>false</c>, gives the <see cref="Exception"/> corresponding to the invocation failure.</param>
|
||||
[JSInvokable(nameof(DotNetDispatcher) + "." + nameof(EndInvoke))]
|
||||
public static void EndInvoke(long asyncHandle, bool succeeded, JSAsyncCallResult result)
|
||||
=> ((JSRuntimeBase)JSRuntime.Current).EndInvokeJS(asyncHandle, succeeded, result.ResultOrException);
|
||||
|
||||
/// <summary>
|
||||
/// 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.
|
||||
/// </summary>
|
||||
/// <param name="dotNetObjectId">The identifier previously passed to JavaScript code.</param>
|
||||
[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<string, (MethodInfo, Type[])> 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<string, (MethodInfo, Type[])>();
|
||||
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<JSInvokableAttribute>(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}'.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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
|
||||
{
|
||||
/// <summary>
|
||||
/// 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.
|
||||
/// </summary>
|
||||
public class DotNetObjectRef : IDisposable
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the object instance represented by this wrapper.
|
||||
/// </summary>
|
||||
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;
|
||||
|
||||
/// <summary>
|
||||
/// Constructs an instance of <see cref="DotNetObjectRef"/>.
|
||||
/// </summary>
|
||||
/// <param name="value">The value being wrapped.</param>
|
||||
public DotNetObjectRef(object value)
|
||||
{
|
||||
Value = value;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Ensures the <see cref="DotNetObjectRef"/> is associated with the specified <see cref="IJSRuntime"/>.
|
||||
/// Developers do not normally need to invoke this manually, since it is called automatically by
|
||||
/// framework code.
|
||||
/// </summary>
|
||||
/// <param name="runtime">The <see cref="IJSRuntime"/>.</param>
|
||||
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.");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 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.
|
||||
/// </summary>
|
||||
public void Dispose()
|
||||
{
|
||||
_attachedToRuntime?.UntrackObjectRef(this);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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).
|
||||
|
||||
/// <summary>
|
||||
/// Internal. Intended for framework use only.
|
||||
/// </summary>
|
||||
public interface ICustomArgSerializer
|
||||
{
|
||||
/// <summary>
|
||||
/// Internal. Intended for framework use only.
|
||||
/// </summary>
|
||||
object ToJsonPrimitive();
|
||||
}
|
||||
}
|
||||
|
|
@ -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
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents an instance of a JavaScript runtime to which calls may be dispatched.
|
||||
/// </summary>
|
||||
public interface IJSInProcessRuntime : IJSRuntime
|
||||
{
|
||||
/// <summary>
|
||||
/// Invokes the specified JavaScript function synchronously.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The JSON-serializable return type.</typeparam>
|
||||
/// <param name="identifier">An identifier for the function to invoke. For example, the value <code>"someScope.someFunction"</code> will invoke the function <code>window.someScope.someFunction</code>.</param>
|
||||
/// <param name="args">JSON-serializable arguments.</param>
|
||||
/// <returns>An instance of <typeparamref name="T"/> obtained by JSON-deserializing the return value.</returns>
|
||||
T Invoke<T>(string identifier, params object[] args);
|
||||
}
|
||||
}
|
||||
|
|
@ -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
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents an instance of a JavaScript runtime to which calls may be dispatched.
|
||||
/// </summary>
|
||||
public interface IJSRuntime
|
||||
{
|
||||
/// <summary>
|
||||
/// Invokes the specified JavaScript function asynchronously.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The JSON-serializable return type.</typeparam>
|
||||
/// <param name="identifier">An identifier for the function to invoke. For example, the value <code>"someScope.someFunction"</code> will invoke the function <code>window.someScope.someFunction</code>.</param>
|
||||
/// <param name="args">JSON-serializable arguments.</param>
|
||||
/// <returns>An instance of <typeparamref name="T"/> obtained by JSON-deserializing the return value.</returns>
|
||||
Task<T> InvokeAsync<T>(string identifier, params object[] args);
|
||||
|
||||
/// <summary>
|
||||
/// Stops tracking the .NET object represented by the <see cref="DotNetObjectRef"/>.
|
||||
/// 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.
|
||||
/// </summary>
|
||||
/// <param name="dotNetObjectRef">The reference to stop tracking.</param>
|
||||
/// <remarks>This method is called automatically by <see cref="DotNetObjectRef.Dispose"/>.</remarks>
|
||||
void UntrackObjectRef(DotNetObjectRef dotNetObjectRef);
|
||||
}
|
||||
}
|
||||
|
|
@ -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<long, DotNetObjectRef> _trackedRefsById = new Dictionary<long, DotNetObjectRef>();
|
||||
private Dictionary<DotNetObjectRef, long> _trackedIdsByRef = new Dictionary<DotNetObjectRef, long>();
|
||||
|
||||
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));
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Stops tracking the specified .NET object reference.
|
||||
/// This overload is typically invoked from JS code via JS interop.
|
||||
/// </summary>
|
||||
/// <param name="dotNetObjectId">The ID of the <see cref="DotNetObjectRef"/>.</param>
|
||||
public void ReleaseDotNetObject(long dotNetObjectId)
|
||||
{
|
||||
lock (_storageLock)
|
||||
{
|
||||
if (_trackedRefsById.TryGetValue(dotNetObjectId, out var dotNetObjectRef))
|
||||
{
|
||||
_trackedRefsById.Remove(dotNetObjectId);
|
||||
_trackedIdsByRef.Remove(dotNetObjectRef);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Stops tracking the specified .NET object reference.
|
||||
/// This overload is typically invoked from .NET code by <see cref="DotNetObjectRef.Dispose"/>.
|
||||
/// </summary>
|
||||
/// <param name="dotNetObjectRef">The <see cref="DotNetObjectRef"/>.</param>
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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<T>. 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<T>.
|
||||
//
|
||||
// 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.
|
||||
|
||||
/// <summary>
|
||||
/// Intended for framework use only.
|
||||
/// </summary>
|
||||
public class JSAsyncCallResult
|
||||
{
|
||||
internal object ResultOrException { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Constructs an instance of <see cref="JSAsyncCallResult"/>.
|
||||
/// </summary>
|
||||
/// <param name="resultOrException">The result of the call.</param>
|
||||
internal JSAsyncCallResult(object resultOrException)
|
||||
{
|
||||
ResultOrException = resultOrException;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents errors that occur during an interop call from .NET to JavaScript.
|
||||
/// </summary>
|
||||
public class JSException : Exception
|
||||
{
|
||||
/// <summary>
|
||||
/// Constructs an instance of <see cref="JSException"/>.
|
||||
/// </summary>
|
||||
/// <param name="message">The exception message.</param>
|
||||
public JSException(string message) : base(message)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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
|
||||
{
|
||||
/// <summary>
|
||||
/// Abstract base class for an in-process JavaScript runtime.
|
||||
/// </summary>
|
||||
public abstract class JSInProcessRuntimeBase : JSRuntimeBase, IJSInProcessRuntime
|
||||
{
|
||||
/// <summary>
|
||||
/// Invokes the specified JavaScript function synchronously.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The JSON-serializable return type.</typeparam>
|
||||
/// <param name="identifier">An identifier for the function to invoke. For example, the value <code>"someScope.someFunction"</code> will invoke the function <code>window.someScope.someFunction</code>.</param>
|
||||
/// <param name="args">JSON-serializable arguments.</param>
|
||||
/// <returns>An instance of <typeparamref name="T"/> obtained by JSON-deserializing the return value.</returns>
|
||||
public T Invoke<T>(string identifier, params object[] args)
|
||||
{
|
||||
var resultJson = InvokeJS(identifier, Json.Serialize(args, ArgSerializerStrategy));
|
||||
return Json.Deserialize<T>(resultJson, ArgSerializerStrategy);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Performs a synchronous function invocation.
|
||||
/// </summary>
|
||||
/// <param name="identifier">The identifier for the function to invoke.</param>
|
||||
/// <param name="argsJson">A JSON representation of the arguments.</param>
|
||||
/// <returns>A JSON representation of the result.</returns>
|
||||
protected abstract string InvokeJS(string identifier, string argsJson);
|
||||
}
|
||||
}
|
||||
|
|
@ -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
|
||||
{
|
||||
/// <summary>
|
||||
/// 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.
|
||||
/// </summary>
|
||||
[AttributeUsage(AttributeTargets.Method, AllowMultiple = true)]
|
||||
public class JSInvokableAttribute : Attribute
|
||||
{
|
||||
/// <summary>
|
||||
/// 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.
|
||||
/// </summary>
|
||||
public string Identifier { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Constructs an instance of <see cref="JSInvokableAttribute"/> without setting
|
||||
/// an identifier for the method.
|
||||
/// </summary>
|
||||
public JSInvokableAttribute()
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Constructs an instance of <see cref="JSInvokableAttribute"/> using the specified
|
||||
/// identifier.
|
||||
/// </summary>
|
||||
/// <param name="identifier">An identifier for the method, which must be unique within the scope of the assembly.</param>
|
||||
public JSInvokableAttribute(string identifier)
|
||||
{
|
||||
if (string.IsNullOrEmpty(identifier))
|
||||
{
|
||||
throw new ArgumentException("Cannot be null or empty", nameof(identifier));
|
||||
}
|
||||
|
||||
Identifier = identifier;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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
|
||||
{
|
||||
/// <summary>
|
||||
/// Provides mechanisms for accessing the current <see cref="IJSRuntime"/>.
|
||||
/// </summary>
|
||||
public static class JSRuntime
|
||||
{
|
||||
private static readonly AsyncLocal<IJSRuntime> _currentJSRuntime = new AsyncLocal<IJSRuntime>();
|
||||
|
||||
internal static IJSRuntime Current => _currentJSRuntime.Value;
|
||||
|
||||
/// <summary>
|
||||
/// Sets the current JS runtime to the supplied instance.
|
||||
///
|
||||
/// This is intended for framework use. Developers should not normally need to call this method.
|
||||
/// </summary>
|
||||
/// <param name="instance">The new current <see cref="IJSRuntime"/>.</param>
|
||||
public static void SetCurrentJSRuntime(IJSRuntime instance)
|
||||
{
|
||||
_currentJSRuntime.Value = instance
|
||||
?? throw new ArgumentNullException(nameof(instance));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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
|
||||
{
|
||||
/// <summary>
|
||||
/// Abstract base class for a JavaScript runtime.
|
||||
/// </summary>
|
||||
public abstract class JSRuntimeBase : IJSRuntime
|
||||
{
|
||||
private long _nextPendingTaskId = 1; // Start at 1 because zero signals "no response needed"
|
||||
private readonly ConcurrentDictionary<long, object> _pendingTasks
|
||||
= new ConcurrentDictionary<long, object>();
|
||||
|
||||
internal InteropArgSerializerStrategy ArgSerializerStrategy { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Constructs an instance of <see cref="JSRuntimeBase"/>.
|
||||
/// </summary>
|
||||
public JSRuntimeBase()
|
||||
{
|
||||
ArgSerializerStrategy = new InteropArgSerializerStrategy(this);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void UntrackObjectRef(DotNetObjectRef dotNetObjectRef)
|
||||
=> ArgSerializerStrategy.ReleaseDotNetObject(dotNetObjectRef);
|
||||
|
||||
/// <summary>
|
||||
/// Invokes the specified JavaScript function asynchronously.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The JSON-serializable return type.</typeparam>
|
||||
/// <param name="identifier">An identifier for the function to invoke. For example, the value <code>"someScope.someFunction"</code> will invoke the function <code>window.someScope.someFunction</code>.</param>
|
||||
/// <param name="args">JSON-serializable arguments.</param>
|
||||
/// <returns>An instance of <typeparamref name="T"/> obtained by JSON-deserializing the return value.</returns>
|
||||
public Task<T> InvokeAsync<T>(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<T>();
|
||||
_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;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Begins an asynchronous function invocation.
|
||||
/// </summary>
|
||||
/// <param name="asyncHandle">The identifier for the function invocation, or zero if no async callback is required.</param>
|
||||
/// <param name="identifier">The identifier for the function to invoke.</param>
|
||||
/// <param name="argsJson">A JSON representation of the arguments.</param>
|
||||
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()));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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
|
||||
{
|
||||
/// <summary>
|
||||
/// Provides mechanisms for converting between .NET objects and JSON strings for use
|
||||
/// when making calls to JavaScript functions via <see cref="IJSRuntime"/>.
|
||||
///
|
||||
/// Warning: This is not intended as a general-purpose JSON library. It is only intended
|
||||
/// for use when making calls via <see cref="IJSRuntime"/>. Eventually its implementation
|
||||
/// will be replaced by something more general-purpose.
|
||||
/// </summary>
|
||||
public static class Json
|
||||
{
|
||||
/// <summary>
|
||||
/// Serializes the value as a JSON string.
|
||||
/// </summary>
|
||||
/// <param name="value">The value to serialize.</param>
|
||||
/// <returns>The JSON string.</returns>
|
||||
public static string Serialize(object value)
|
||||
=> SimpleJson.SimpleJson.SerializeObject(value);
|
||||
|
||||
internal static string Serialize(object value, SimpleJson.IJsonSerializerStrategy serializerStrategy)
|
||||
=> SimpleJson.SimpleJson.SerializeObject(value, serializerStrategy);
|
||||
|
||||
/// <summary>
|
||||
/// Deserializes the JSON string, creating an object of the specified generic type.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type of object to create.</typeparam>
|
||||
/// <param name="json">The JSON string.</param>
|
||||
/// <returns>An object of the specified type.</returns>
|
||||
public static T Deserialize<T>(string json)
|
||||
=> SimpleJson.SimpleJson.DeserializeObject<T>(json);
|
||||
|
||||
internal static T Deserialize<T>(string json, SimpleJson.IJsonSerializerStrategy serializerStrategy)
|
||||
=> SimpleJson.SimpleJson.DeserializeObject<T>(json, serializerStrategy);
|
||||
}
|
||||
}
|
||||
|
|
@ -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.
|
||||
File diff suppressed because it is too large
Load Diff
|
|
@ -0,0 +1,15 @@
|
|||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>netstandard2.0</TargetFramework>
|
||||
<Description>Abstractions and features for interop between .NET and JavaScript code.</Description>
|
||||
<PackageTags>javascript;interop</PackageTags>
|
||||
<GenerateDocumentationFile>true</GenerateDocumentationFile>
|
||||
<IsShipping>true</IsShipping>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<InternalsVisibleTo Include="Microsoft.JSInterop.Tests" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
|
@ -0,0 +1,116 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Microsoft.JSInterop
|
||||
{
|
||||
internal static class TaskGenericsUtil
|
||||
{
|
||||
private static ConcurrentDictionary<Type, ITaskResultGetter> _cachedResultGetters
|
||||
= new ConcurrentDictionary<Type, ITaskResultGetter>();
|
||||
|
||||
private static ConcurrentDictionary<Type, ITcsResultSetter> _cachedResultSetters
|
||||
= new ConcurrentDictionary<Type, ITcsResultSetter>();
|
||||
|
||||
public static void SetTaskCompletionSourceResult(object taskCompletionSource, object result)
|
||||
=> CreateResultSetter(taskCompletionSource).SetResult(taskCompletionSource, result);
|
||||
|
||||
public static void SetTaskCompletionSourceException(object taskCompletionSource, Exception exception)
|
||||
=> CreateResultSetter(taskCompletionSource).SetException(taskCompletionSource, exception);
|
||||
|
||||
public static Type GetTaskCompletionSourceResultType(object taskCompletionSource)
|
||||
=> CreateResultSetter(taskCompletionSource).ResultType;
|
||||
|
||||
public static object GetTaskResult(Task task)
|
||||
{
|
||||
var getter = _cachedResultGetters.GetOrAdd(task.GetType(), taskInstanceType =>
|
||||
{
|
||||
var resultType = GetTaskResultType(taskInstanceType);
|
||||
return resultType == null
|
||||
? new VoidTaskResultGetter()
|
||||
: (ITaskResultGetter)Activator.CreateInstance(
|
||||
typeof(TaskResultGetter<>).MakeGenericType(resultType));
|
||||
});
|
||||
return getter.GetResult(task);
|
||||
}
|
||||
|
||||
private static Type GetTaskResultType(Type taskType)
|
||||
{
|
||||
// It might be something derived from Task or Task<T>, so we have to scan
|
||||
// up the inheritance hierarchy to find the Task or Task<T>
|
||||
while (taskType != typeof(Task) &&
|
||||
(!taskType.IsGenericType || taskType.GetGenericTypeDefinition() != typeof(Task<>)))
|
||||
{
|
||||
taskType = taskType.BaseType
|
||||
?? throw new ArgumentException($"The type '{taskType.FullName}' is not inherited from '{typeof(Task).FullName}'.");
|
||||
}
|
||||
|
||||
return taskType.IsGenericType
|
||||
? taskType.GetGenericArguments().Single()
|
||||
: null;
|
||||
}
|
||||
|
||||
interface ITcsResultSetter
|
||||
{
|
||||
Type ResultType { get; }
|
||||
void SetResult(object taskCompletionSource, object result);
|
||||
void SetException(object taskCompletionSource, Exception exception);
|
||||
}
|
||||
|
||||
private interface ITaskResultGetter
|
||||
{
|
||||
object GetResult(Task task);
|
||||
}
|
||||
|
||||
private class TaskResultGetter<T> : ITaskResultGetter
|
||||
{
|
||||
public object GetResult(Task task) => ((Task<T>)task).Result;
|
||||
}
|
||||
|
||||
private class VoidTaskResultGetter : ITaskResultGetter
|
||||
{
|
||||
public object GetResult(Task task)
|
||||
{
|
||||
task.Wait(); // Throw if the task failed
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private class TcsResultSetter<T> : ITcsResultSetter
|
||||
{
|
||||
public Type ResultType => typeof(T);
|
||||
|
||||
public void SetResult(object tcs, object result)
|
||||
{
|
||||
var typedTcs = (TaskCompletionSource<T>)tcs;
|
||||
|
||||
// If necessary, attempt a cast
|
||||
var typedResult = result is T resultT
|
||||
? resultT
|
||||
: (T)Convert.ChangeType(result, typeof(T));
|
||||
|
||||
typedTcs.SetResult(typedResult);
|
||||
}
|
||||
|
||||
public void SetException(object tcs, Exception exception)
|
||||
{
|
||||
var typedTcs = (TaskCompletionSource<T>)tcs;
|
||||
typedTcs.SetException(exception);
|
||||
}
|
||||
}
|
||||
|
||||
private static ITcsResultSetter CreateResultSetter(object taskCompletionSource)
|
||||
{
|
||||
return _cachedResultSetters.GetOrAdd(taskCompletionSource.GetType(), tcsType =>
|
||||
{
|
||||
var resultType = tcsType.GetGenericArguments().Single();
|
||||
return (ITcsResultSetter)Activator.CreateInstance(
|
||||
typeof(TcsResultSetter<>).MakeGenericType(resultType));
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,508 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Xunit;
|
||||
|
||||
namespace Microsoft.JSInterop.Tests
|
||||
{
|
||||
public class DotNetDispatcherTest
|
||||
{
|
||||
private readonly static string thisAssemblyName
|
||||
= typeof(DotNetDispatcherTest).Assembly.GetName().Name;
|
||||
private readonly TestJSRuntime jsRuntime
|
||||
= new TestJSRuntime();
|
||||
|
||||
[Fact]
|
||||
public void CannotInvokeWithEmptyAssemblyName()
|
||||
{
|
||||
var ex = Assert.Throws<ArgumentException>(() =>
|
||||
{
|
||||
DotNetDispatcher.Invoke(" ", "SomeMethod", default, "[]");
|
||||
});
|
||||
|
||||
Assert.StartsWith("Cannot be null, empty, or whitespace.", ex.Message);
|
||||
Assert.Equal("assemblyName", ex.ParamName);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CannotInvokeWithEmptyMethodIdentifier()
|
||||
{
|
||||
var ex = Assert.Throws<ArgumentException>(() =>
|
||||
{
|
||||
DotNetDispatcher.Invoke("SomeAssembly", " ", default, "[]");
|
||||
});
|
||||
|
||||
Assert.StartsWith("Cannot be null, empty, or whitespace.", ex.Message);
|
||||
Assert.Equal("methodIdentifier", ex.ParamName);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CannotInvokeMethodsOnUnloadedAssembly()
|
||||
{
|
||||
var assemblyName = "Some.Fake.Assembly";
|
||||
var ex = Assert.Throws<ArgumentException>(() =>
|
||||
{
|
||||
DotNetDispatcher.Invoke(assemblyName, "SomeMethod", default, null);
|
||||
});
|
||||
|
||||
Assert.Equal($"There is no loaded assembly with the name '{assemblyName}'.", ex.Message);
|
||||
}
|
||||
|
||||
// Note: Currently it's also not possible to invoke generic methods.
|
||||
// That's not something determined by DotNetDispatcher, but rather by the fact that we
|
||||
// don't close over the generics in the reflection code.
|
||||
// Not defining this behavior through unit tests because the default outcome is
|
||||
// fine (an exception stating what info is missing).
|
||||
|
||||
[Theory]
|
||||
[InlineData("MethodOnInternalType")]
|
||||
[InlineData("PrivateMethod")]
|
||||
[InlineData("ProtectedMethod")]
|
||||
[InlineData("StaticMethodWithoutAttribute")] // That's not really its identifier; just making the point that there's no way to invoke it
|
||||
[InlineData("InstanceMethodWithoutAttribute")] // That's not really its identifier; just making the point that there's no way to invoke it
|
||||
public void CannotInvokeUnsuitableMethods(string methodIdentifier)
|
||||
{
|
||||
var ex = Assert.Throws<ArgumentException>(() =>
|
||||
{
|
||||
DotNetDispatcher.Invoke(thisAssemblyName, methodIdentifier, default, null);
|
||||
});
|
||||
|
||||
Assert.Equal($"The assembly '{thisAssemblyName}' does not contain a public method with [JSInvokableAttribute(\"{methodIdentifier}\")].", ex.Message);
|
||||
}
|
||||
|
||||
[Fact(Skip = "https://github.com/aspnet/AspNetCore-Internal/issues/1733")]
|
||||
public Task CanInvokeStaticVoidMethod() => WithJSRuntime(jsRuntime =>
|
||||
{
|
||||
// Arrange/Act
|
||||
SomePublicType.DidInvokeMyInvocableStaticVoid = false;
|
||||
var resultJson = DotNetDispatcher.Invoke(thisAssemblyName, "InvocableStaticVoid", default, null);
|
||||
|
||||
// Assert
|
||||
Assert.Null(resultJson);
|
||||
Assert.True(SomePublicType.DidInvokeMyInvocableStaticVoid);
|
||||
});
|
||||
|
||||
[Fact]
|
||||
public Task CanInvokeStaticNonVoidMethod() => WithJSRuntime(jsRuntime =>
|
||||
{
|
||||
// Arrange/Act
|
||||
var resultJson = DotNetDispatcher.Invoke(thisAssemblyName, "InvocableStaticNonVoid", default, null);
|
||||
var result = Json.Deserialize<TestDTO>(resultJson);
|
||||
|
||||
// Assert
|
||||
Assert.Equal("Test", result.StringVal);
|
||||
Assert.Equal(123, result.IntVal);
|
||||
});
|
||||
|
||||
[Fact]
|
||||
public Task CanInvokeStaticNonVoidMethodWithoutCustomIdentifier() => WithJSRuntime(jsRuntime =>
|
||||
{
|
||||
// Arrange/Act
|
||||
var resultJson = DotNetDispatcher.Invoke(thisAssemblyName, nameof(SomePublicType.InvokableMethodWithoutCustomIdentifier), default, null);
|
||||
var result = Json.Deserialize<TestDTO>(resultJson);
|
||||
|
||||
// Assert
|
||||
Assert.Equal("InvokableMethodWithoutCustomIdentifier", result.StringVal);
|
||||
Assert.Equal(456, result.IntVal);
|
||||
});
|
||||
|
||||
[Fact(Skip = "https://github.com/aspnet/AspNetCore-Internal/issues/1733")]
|
||||
public Task CanInvokeStaticWithParams() => WithJSRuntime(jsRuntime =>
|
||||
{
|
||||
// Arrange: Track a .NET object to use as an arg
|
||||
var arg3 = new TestDTO { IntVal = 999, StringVal = "My string" };
|
||||
jsRuntime.Invoke<object>("unimportant", new DotNetObjectRef(arg3));
|
||||
|
||||
// Arrange: Remaining args
|
||||
var argsJson = Json.Serialize(new object[] {
|
||||
new TestDTO { StringVal = "Another string", IntVal = 456 },
|
||||
new[] { 100, 200 },
|
||||
"__dotNetObject:1"
|
||||
});
|
||||
|
||||
// Act
|
||||
var resultJson = DotNetDispatcher.Invoke(thisAssemblyName, "InvocableStaticWithParams", default, argsJson);
|
||||
var result = Json.Deserialize<object[]>(resultJson);
|
||||
|
||||
// Assert: First result value marshalled via JSON
|
||||
var resultDto1 = (TestDTO)jsRuntime.ArgSerializerStrategy.DeserializeObject(result[0], typeof(TestDTO));
|
||||
Assert.Equal("ANOTHER STRING", resultDto1.StringVal);
|
||||
Assert.Equal(756, resultDto1.IntVal);
|
||||
|
||||
// Assert: Second result value marshalled by ref
|
||||
var resultDto2Ref = (string)result[1];
|
||||
Assert.Equal("__dotNetObject:2", resultDto2Ref);
|
||||
var resultDto2 = (TestDTO)jsRuntime.ArgSerializerStrategy.FindDotNetObject(2);
|
||||
Assert.Equal("MY STRING", resultDto2.StringVal);
|
||||
Assert.Equal(1299, resultDto2.IntVal);
|
||||
});
|
||||
|
||||
[Fact(Skip = "https://github.com/aspnet/AspNetCore-Internal/issues/1733")]
|
||||
public Task CanInvokeInstanceVoidMethod() => WithJSRuntime(jsRuntime =>
|
||||
{
|
||||
// Arrange: Track some instance
|
||||
var targetInstance = new SomePublicType();
|
||||
jsRuntime.Invoke<object>("unimportant", new DotNetObjectRef(targetInstance));
|
||||
|
||||
// Act
|
||||
var resultJson = DotNetDispatcher.Invoke(null, "InvokableInstanceVoid", 1, null);
|
||||
|
||||
// Assert
|
||||
Assert.Null(resultJson);
|
||||
Assert.True(targetInstance.DidInvokeMyInvocableInstanceVoid);
|
||||
});
|
||||
|
||||
[Fact(Skip = "https://github.com/aspnet/AspNetCore-Internal/issues/1733")]
|
||||
public Task CanInvokeBaseInstanceVoidMethod() => WithJSRuntime(jsRuntime =>
|
||||
{
|
||||
// Arrange: Track some instance
|
||||
var targetInstance = new DerivedClass();
|
||||
jsRuntime.Invoke<object>("unimportant", new DotNetObjectRef(targetInstance));
|
||||
|
||||
// Act
|
||||
var resultJson = DotNetDispatcher.Invoke(null, "BaseClassInvokableInstanceVoid", 1, null);
|
||||
|
||||
// Assert
|
||||
Assert.Null(resultJson);
|
||||
Assert.True(targetInstance.DidInvokeMyBaseClassInvocableInstanceVoid);
|
||||
});
|
||||
|
||||
[Fact]
|
||||
public Task CannotUseDotNetObjectRefAfterDisposal() => WithJSRuntime(jsRuntime =>
|
||||
{
|
||||
// This test addresses the case where the developer calls objectRef.Dispose()
|
||||
// from .NET code, as opposed to .dispose() from JS code
|
||||
|
||||
// Arrange: Track some instance, then dispose it
|
||||
var targetInstance = new SomePublicType();
|
||||
var objectRef = new DotNetObjectRef(targetInstance);
|
||||
jsRuntime.Invoke<object>("unimportant", objectRef);
|
||||
objectRef.Dispose();
|
||||
|
||||
// Act/Assert
|
||||
var ex = Assert.Throws<ArgumentException>(
|
||||
() => DotNetDispatcher.Invoke(null, "InvokableInstanceVoid", 1, null));
|
||||
Assert.StartsWith("There is no tracked object with id '1'.", ex.Message);
|
||||
});
|
||||
|
||||
[Fact]
|
||||
public Task CannotUseDotNetObjectRefAfterReleaseDotNetObject() => WithJSRuntime(jsRuntime =>
|
||||
{
|
||||
// This test addresses the case where the developer calls .dispose()
|
||||
// from JS code, as opposed to objectRef.Dispose() from .NET code
|
||||
|
||||
// Arrange: Track some instance, then dispose it
|
||||
var targetInstance = new SomePublicType();
|
||||
var objectRef = new DotNetObjectRef(targetInstance);
|
||||
jsRuntime.Invoke<object>("unimportant", objectRef);
|
||||
DotNetDispatcher.ReleaseDotNetObject(1);
|
||||
|
||||
// Act/Assert
|
||||
var ex = Assert.Throws<ArgumentException>(
|
||||
() => DotNetDispatcher.Invoke(null, "InvokableInstanceVoid", 1, null));
|
||||
Assert.StartsWith("There is no tracked object with id '1'.", ex.Message);
|
||||
});
|
||||
|
||||
[Fact(Skip = "https://github.com/aspnet/AspNetCore-Internal/issues/1733")]
|
||||
public Task CanInvokeInstanceMethodWithParams() => WithJSRuntime(jsRuntime =>
|
||||
{
|
||||
// Arrange: Track some instance plus another object we'll pass as a param
|
||||
var targetInstance = new SomePublicType();
|
||||
var arg2 = new TestDTO { IntVal = 1234, StringVal = "My string" };
|
||||
jsRuntime.Invoke<object>("unimportant",
|
||||
new DotNetObjectRef(targetInstance),
|
||||
new DotNetObjectRef(arg2));
|
||||
var argsJson = "[\"myvalue\",\"__dotNetObject:2\"]";
|
||||
|
||||
// Act
|
||||
var resultJson = DotNetDispatcher.Invoke(null, "InvokableInstanceMethod", 1, argsJson);
|
||||
|
||||
// Assert
|
||||
Assert.Equal("[\"You passed myvalue\",\"__dotNetObject:3\"]", resultJson);
|
||||
var resultDto = (TestDTO)jsRuntime.ArgSerializerStrategy.FindDotNetObject(3);
|
||||
Assert.Equal(1235, resultDto.IntVal);
|
||||
Assert.Equal("MY STRING", resultDto.StringVal);
|
||||
});
|
||||
|
||||
[Fact]
|
||||
public void CannotInvokeWithIncorrectNumberOfParams()
|
||||
{
|
||||
// Arrange
|
||||
var argsJson = Json.Serialize(new object[] { 1, 2, 3, 4 });
|
||||
|
||||
// Act/Assert
|
||||
var ex = Assert.Throws<ArgumentException>(() =>
|
||||
{
|
||||
DotNetDispatcher.Invoke(thisAssemblyName, "InvocableStaticWithParams", default, argsJson);
|
||||
});
|
||||
|
||||
Assert.Equal("In call to 'InvocableStaticWithParams', expected 3 parameters but received 4.", ex.Message);
|
||||
}
|
||||
|
||||
[Fact(Skip = "https://github.com/aspnet/AspNetCore-Internal/issues/1733")]
|
||||
public Task CanInvokeAsyncMethod() => WithJSRuntime(async jsRuntime =>
|
||||
{
|
||||
// Arrange: Track some instance plus another object we'll pass as a param
|
||||
var targetInstance = new SomePublicType();
|
||||
var arg2 = new TestDTO { IntVal = 1234, StringVal = "My string" };
|
||||
jsRuntime.Invoke<object>("unimportant", new DotNetObjectRef(targetInstance), new DotNetObjectRef(arg2));
|
||||
|
||||
// Arrange: all args
|
||||
var argsJson = Json.Serialize(new object[]
|
||||
{
|
||||
new TestDTO { IntVal = 1000, StringVal = "String via JSON" },
|
||||
"__dotNetObject:2"
|
||||
});
|
||||
|
||||
// Act
|
||||
var callId = "123";
|
||||
var resultTask = jsRuntime.NextInvocationTask;
|
||||
DotNetDispatcher.BeginInvoke(callId, null, "InvokableAsyncMethod", 1, argsJson);
|
||||
await resultTask;
|
||||
var result = Json.Deserialize<SimpleJson.JsonArray>(jsRuntime.LastInvocationArgsJson);
|
||||
var resultValue = (SimpleJson.JsonArray)result[2];
|
||||
|
||||
// Assert: Correct info to complete the async call
|
||||
Assert.Equal(0, jsRuntime.LastInvocationAsyncHandle); // 0 because it doesn't want a further callback from JS to .NET
|
||||
Assert.Equal("DotNet.jsCallDispatcher.endInvokeDotNetFromJS", jsRuntime.LastInvocationIdentifier);
|
||||
Assert.Equal(3, result.Count);
|
||||
Assert.Equal(callId, result[0]);
|
||||
Assert.True((bool)result[1]); // Success flag
|
||||
|
||||
// Assert: First result value marshalled via JSON
|
||||
var resultDto1 = (TestDTO)jsRuntime.ArgSerializerStrategy.DeserializeObject(resultValue[0], typeof(TestDTO));
|
||||
Assert.Equal("STRING VIA JSON", resultDto1.StringVal);
|
||||
Assert.Equal(2000, resultDto1.IntVal);
|
||||
|
||||
// Assert: Second result value marshalled by ref
|
||||
var resultDto2Ref = (string)resultValue[1];
|
||||
Assert.Equal("__dotNetObject:3", resultDto2Ref);
|
||||
var resultDto2 = (TestDTO)jsRuntime.ArgSerializerStrategy.FindDotNetObject(3);
|
||||
Assert.Equal("MY STRING", resultDto2.StringVal);
|
||||
Assert.Equal(2468, resultDto2.IntVal);
|
||||
});
|
||||
|
||||
|
||||
[Fact]
|
||||
public Task CanInvokeSyncThrowingMethod() => WithJSRuntime(async jsRuntime =>
|
||||
{
|
||||
// Arrange
|
||||
|
||||
// Act
|
||||
var callId = "123";
|
||||
var resultTask = jsRuntime.NextInvocationTask;
|
||||
DotNetDispatcher.BeginInvoke(callId, thisAssemblyName, nameof(ThrowingClass.ThrowingMethod), default, default);
|
||||
|
||||
await resultTask; // This won't throw, it sets properties on the jsRuntime.
|
||||
|
||||
// Assert
|
||||
var result = Json.Deserialize<SimpleJson.JsonArray>(jsRuntime.LastInvocationArgsJson);
|
||||
Assert.Equal(callId, result[0]);
|
||||
Assert.False((bool)result[1]); // Fails
|
||||
|
||||
// Make sure the method that threw the exception shows up in the call stack
|
||||
// https://github.com/aspnet/AspNetCore/issues/8612
|
||||
var exception = (string)result[2];
|
||||
Assert.Contains(nameof(ThrowingClass.ThrowingMethod), exception);
|
||||
});
|
||||
|
||||
[Fact]
|
||||
public Task CanInvokeAsyncThrowingMethod() => WithJSRuntime(async jsRuntime =>
|
||||
{
|
||||
// Arrange
|
||||
|
||||
// Act
|
||||
var callId = "123";
|
||||
var resultTask = jsRuntime.NextInvocationTask;
|
||||
DotNetDispatcher.BeginInvoke(callId, thisAssemblyName, nameof(ThrowingClass.AsyncThrowingMethod), default, default);
|
||||
|
||||
await resultTask; // This won't throw, it sets properties on the jsRuntime.
|
||||
|
||||
// Assert
|
||||
var result = Json.Deserialize<SimpleJson.JsonArray>(jsRuntime.LastInvocationArgsJson);
|
||||
Assert.Equal(callId, result[0]);
|
||||
Assert.False((bool)result[1]); // Fails
|
||||
|
||||
// Make sure the method that threw the exception shows up in the call stack
|
||||
// https://github.com/aspnet/AspNetCore/issues/8612
|
||||
var exception = (string)result[2];
|
||||
Assert.Contains(nameof(ThrowingClass.AsyncThrowingMethod), exception);
|
||||
});
|
||||
|
||||
|
||||
Task WithJSRuntime(Action<TestJSRuntime> testCode)
|
||||
{
|
||||
return WithJSRuntime(jsRuntime =>
|
||||
{
|
||||
testCode(jsRuntime);
|
||||
return Task.CompletedTask;
|
||||
});
|
||||
}
|
||||
|
||||
async Task WithJSRuntime(Func<TestJSRuntime, Task> testCode)
|
||||
{
|
||||
// Since the tests rely on the asynclocal JSRuntime.Current, ensure we
|
||||
// are on a distinct async context with a non-null JSRuntime.Current
|
||||
await Task.Yield();
|
||||
|
||||
var runtime = new TestJSRuntime();
|
||||
JSRuntime.SetCurrentJSRuntime(runtime);
|
||||
await testCode(runtime);
|
||||
}
|
||||
|
||||
internal class SomeInteralType
|
||||
{
|
||||
[JSInvokable("MethodOnInternalType")] public void MyMethod() { }
|
||||
}
|
||||
|
||||
public class SomePublicType
|
||||
{
|
||||
public static bool DidInvokeMyInvocableStaticVoid;
|
||||
public bool DidInvokeMyInvocableInstanceVoid;
|
||||
|
||||
[JSInvokable("PrivateMethod")] private static void MyPrivateMethod() { }
|
||||
[JSInvokable("ProtectedMethod")] protected static void MyProtectedMethod() { }
|
||||
protected static void StaticMethodWithoutAttribute() { }
|
||||
protected static void InstanceMethodWithoutAttribute() { }
|
||||
|
||||
[JSInvokable("InvocableStaticVoid")]
|
||||
public static void MyInvocableVoid()
|
||||
{
|
||||
DidInvokeMyInvocableStaticVoid = true;
|
||||
}
|
||||
|
||||
[JSInvokable("InvocableStaticNonVoid")]
|
||||
public static object MyInvocableNonVoid()
|
||||
=> new TestDTO { StringVal = "Test", IntVal = 123 };
|
||||
|
||||
[JSInvokable("InvocableStaticWithParams")]
|
||||
public static object[] MyInvocableWithParams(TestDTO dtoViaJson, int[] incrementAmounts, TestDTO dtoByRef)
|
||||
=> new object[]
|
||||
{
|
||||
new TestDTO // Return via JSON marshalling
|
||||
{
|
||||
StringVal = dtoViaJson.StringVal.ToUpperInvariant(),
|
||||
IntVal = dtoViaJson.IntVal + incrementAmounts.Sum()
|
||||
},
|
||||
new DotNetObjectRef(new TestDTO // Return by ref
|
||||
{
|
||||
StringVal = dtoByRef.StringVal.ToUpperInvariant(),
|
||||
IntVal = dtoByRef.IntVal + incrementAmounts.Sum()
|
||||
})
|
||||
};
|
||||
|
||||
[JSInvokable]
|
||||
public static TestDTO InvokableMethodWithoutCustomIdentifier()
|
||||
=> new TestDTO { StringVal = "InvokableMethodWithoutCustomIdentifier", IntVal = 456 };
|
||||
|
||||
[JSInvokable]
|
||||
public void InvokableInstanceVoid()
|
||||
{
|
||||
DidInvokeMyInvocableInstanceVoid = true;
|
||||
}
|
||||
|
||||
[JSInvokable]
|
||||
public object[] InvokableInstanceMethod(string someString, TestDTO someDTO)
|
||||
{
|
||||
// Returning an array to make the point that object references
|
||||
// can be embedded anywhere in the result
|
||||
return new object[]
|
||||
{
|
||||
$"You passed {someString}",
|
||||
new DotNetObjectRef(new TestDTO
|
||||
{
|
||||
IntVal = someDTO.IntVal + 1,
|
||||
StringVal = someDTO.StringVal.ToUpperInvariant()
|
||||
})
|
||||
};
|
||||
}
|
||||
|
||||
[JSInvokable]
|
||||
public async Task<object[]> InvokableAsyncMethod(TestDTO dtoViaJson, TestDTO dtoByRef)
|
||||
{
|
||||
await Task.Delay(50);
|
||||
return new object[]
|
||||
{
|
||||
new TestDTO // Return via JSON
|
||||
{
|
||||
StringVal = dtoViaJson.StringVal.ToUpperInvariant(),
|
||||
IntVal = dtoViaJson.IntVal * 2,
|
||||
},
|
||||
new DotNetObjectRef(new TestDTO // Return by ref
|
||||
{
|
||||
StringVal = dtoByRef.StringVal.ToUpperInvariant(),
|
||||
IntVal = dtoByRef.IntVal * 2,
|
||||
})
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
public class BaseClass
|
||||
{
|
||||
public bool DidInvokeMyBaseClassInvocableInstanceVoid;
|
||||
|
||||
[JSInvokable]
|
||||
public void BaseClassInvokableInstanceVoid()
|
||||
{
|
||||
DidInvokeMyBaseClassInvocableInstanceVoid = true;
|
||||
}
|
||||
}
|
||||
|
||||
public class DerivedClass : BaseClass
|
||||
{
|
||||
}
|
||||
|
||||
public class TestDTO
|
||||
{
|
||||
public string StringVal { get; set; }
|
||||
public int IntVal { get; set; }
|
||||
}
|
||||
|
||||
public class ThrowingClass
|
||||
{
|
||||
[JSInvokable]
|
||||
public static string ThrowingMethod()
|
||||
{
|
||||
throw new InvalidTimeZoneException();
|
||||
}
|
||||
|
||||
[JSInvokable]
|
||||
public static async Task<string> AsyncThrowingMethod()
|
||||
{
|
||||
await Task.Yield();
|
||||
throw new InvalidTimeZoneException();
|
||||
}
|
||||
}
|
||||
|
||||
public class TestJSRuntime : JSInProcessRuntimeBase
|
||||
{
|
||||
private TaskCompletionSource<object> _nextInvocationTcs = new TaskCompletionSource<object>();
|
||||
public Task NextInvocationTask => _nextInvocationTcs.Task;
|
||||
public long LastInvocationAsyncHandle { get; private set; }
|
||||
public string LastInvocationIdentifier { get; private set; }
|
||||
public string LastInvocationArgsJson { get; private set; }
|
||||
|
||||
protected override void BeginInvokeJS(long asyncHandle, string identifier, string argsJson)
|
||||
{
|
||||
LastInvocationAsyncHandle = asyncHandle;
|
||||
LastInvocationIdentifier = identifier;
|
||||
LastInvocationArgsJson = argsJson;
|
||||
_nextInvocationTcs.SetResult(null);
|
||||
_nextInvocationTcs = new TaskCompletionSource<object>();
|
||||
}
|
||||
|
||||
protected override string InvokeJS(string identifier, string argsJson)
|
||||
{
|
||||
LastInvocationAsyncHandle = default;
|
||||
LastInvocationIdentifier = identifier;
|
||||
LastInvocationArgsJson = argsJson;
|
||||
_nextInvocationTcs.SetResult(null);
|
||||
_nextInvocationTcs = new TaskCompletionSource<object>();
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,68 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading.Tasks;
|
||||
using Xunit;
|
||||
|
||||
namespace Microsoft.JSInterop.Tests
|
||||
{
|
||||
public class DotNetObjectRefTest
|
||||
{
|
||||
[Fact]
|
||||
public void CanAccessValue()
|
||||
{
|
||||
var obj = new object();
|
||||
Assert.Same(obj, new DotNetObjectRef(obj).Value);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CanAssociateWithSameRuntimeMultipleTimes()
|
||||
{
|
||||
var objRef = new DotNetObjectRef(new object());
|
||||
var jsRuntime = new TestJsRuntime();
|
||||
objRef.EnsureAttachedToJsRuntime(jsRuntime);
|
||||
objRef.EnsureAttachedToJsRuntime(jsRuntime);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CannotAssociateWithDifferentRuntimes()
|
||||
{
|
||||
var objRef = new DotNetObjectRef(new object());
|
||||
var jsRuntime1 = new TestJsRuntime();
|
||||
var jsRuntime2 = new TestJsRuntime();
|
||||
objRef.EnsureAttachedToJsRuntime(jsRuntime1);
|
||||
|
||||
var ex = Assert.Throws<InvalidOperationException>(
|
||||
() => objRef.EnsureAttachedToJsRuntime(jsRuntime2));
|
||||
Assert.Contains("Do not attempt to re-use", ex.Message);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void NotifiesAssociatedJsRuntimeOfDisposal()
|
||||
{
|
||||
// Arrange
|
||||
var objRef = new DotNetObjectRef(new object());
|
||||
var jsRuntime = new TestJsRuntime();
|
||||
objRef.EnsureAttachedToJsRuntime(jsRuntime);
|
||||
|
||||
// Act
|
||||
objRef.Dispose();
|
||||
|
||||
// Assert
|
||||
Assert.Equal(new[] { objRef }, jsRuntime.UntrackedRefs);
|
||||
}
|
||||
|
||||
class TestJsRuntime : IJSRuntime
|
||||
{
|
||||
public List<DotNetObjectRef> UntrackedRefs = new List<DotNetObjectRef>();
|
||||
|
||||
public Task<T> InvokeAsync<T>(string identifier, params object[] args)
|
||||
=> throw new NotImplementedException();
|
||||
|
||||
public void UntrackObjectRef(DotNetObjectRef dotNetObjectRef)
|
||||
=> UntrackedRefs.Add(dotNetObjectRef);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,117 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Xunit;
|
||||
|
||||
namespace Microsoft.JSInterop.Tests
|
||||
{
|
||||
public class JSInProcessRuntimeBaseTest
|
||||
{
|
||||
[Fact(Skip = "https://github.com/aspnet/AspNetCore-Internal/issues/1807#issuecomment-470756811")]
|
||||
public void DispatchesSyncCallsAndDeserializesResults()
|
||||
{
|
||||
// Arrange
|
||||
var runtime = new TestJSInProcessRuntime
|
||||
{
|
||||
NextResultJson = Json.Serialize(
|
||||
new TestDTO { IntValue = 123, StringValue = "Hello" })
|
||||
};
|
||||
|
||||
// Act
|
||||
var syncResult = runtime.Invoke<TestDTO>("test identifier 1", "arg1", 123, true);
|
||||
var call = runtime.InvokeCalls.Single();
|
||||
|
||||
// Assert
|
||||
Assert.Equal(123, syncResult.IntValue);
|
||||
Assert.Equal("Hello", syncResult.StringValue);
|
||||
Assert.Equal("test identifier 1", call.Identifier);
|
||||
Assert.Equal("[\"arg1\",123,true]", call.ArgsJson);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void SerializesDotNetObjectWrappersInKnownFormat()
|
||||
{
|
||||
// Arrange
|
||||
var runtime = new TestJSInProcessRuntime { NextResultJson = null };
|
||||
var obj1 = new object();
|
||||
var obj2 = new object();
|
||||
var obj3 = new object();
|
||||
|
||||
// Act
|
||||
// Showing we can pass the DotNetObject either as top-level args or nested
|
||||
var syncResult = runtime.Invoke<object>("test identifier",
|
||||
new DotNetObjectRef(obj1),
|
||||
new Dictionary<string, object>
|
||||
{
|
||||
{ "obj2", new DotNetObjectRef(obj2) },
|
||||
{ "obj3", new DotNetObjectRef(obj3) }
|
||||
});
|
||||
|
||||
// Assert: Handles null result string
|
||||
Assert.Null(syncResult);
|
||||
|
||||
// Assert: Serialized as expected
|
||||
var call = runtime.InvokeCalls.Single();
|
||||
Assert.Equal("test identifier", call.Identifier);
|
||||
Assert.Equal("[\"__dotNetObject:1\",{\"obj2\":\"__dotNetObject:2\",\"obj3\":\"__dotNetObject:3\"}]", call.ArgsJson);
|
||||
|
||||
// Assert: Objects were tracked
|
||||
Assert.Same(obj1, runtime.ArgSerializerStrategy.FindDotNetObject(1));
|
||||
Assert.Same(obj2, runtime.ArgSerializerStrategy.FindDotNetObject(2));
|
||||
Assert.Same(obj3, runtime.ArgSerializerStrategy.FindDotNetObject(3));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void SyncCallResultCanIncludeDotNetObjects()
|
||||
{
|
||||
// Arrange
|
||||
var runtime = new TestJSInProcessRuntime
|
||||
{
|
||||
NextResultJson = "[\"__dotNetObject:2\",\"__dotNetObject:1\"]"
|
||||
};
|
||||
var obj1 = new object();
|
||||
var obj2 = new object();
|
||||
|
||||
// Act
|
||||
var syncResult = runtime.Invoke<object[]>("test identifier",
|
||||
new DotNetObjectRef(obj1),
|
||||
"some other arg",
|
||||
new DotNetObjectRef(obj2));
|
||||
var call = runtime.InvokeCalls.Single();
|
||||
|
||||
// Assert
|
||||
Assert.Equal(new[] { obj2, obj1 }, syncResult);
|
||||
}
|
||||
|
||||
class TestDTO
|
||||
{
|
||||
public int IntValue { get; set; }
|
||||
public string StringValue { get; set; }
|
||||
}
|
||||
|
||||
class TestJSInProcessRuntime : JSInProcessRuntimeBase
|
||||
{
|
||||
public List<InvokeArgs> InvokeCalls { get; set; } = new List<InvokeArgs>();
|
||||
|
||||
public string NextResultJson { get; set; }
|
||||
|
||||
protected override string InvokeJS(string identifier, string argsJson)
|
||||
{
|
||||
InvokeCalls.Add(new InvokeArgs { Identifier = identifier, ArgsJson = argsJson });
|
||||
return NextResultJson;
|
||||
}
|
||||
|
||||
public class InvokeArgs
|
||||
{
|
||||
public string Identifier { get; set; }
|
||||
public string ArgsJson { get; set; }
|
||||
}
|
||||
|
||||
protected override void BeginInvokeJS(long asyncHandle, string identifier, string argsJson)
|
||||
=> throw new NotImplementedException("This test only covers sync calls");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,191 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using Microsoft.JSInterop.Internal;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Xunit;
|
||||
|
||||
namespace Microsoft.JSInterop.Tests
|
||||
{
|
||||
public class JSRuntimeBaseTest
|
||||
{
|
||||
[Fact]
|
||||
public void DispatchesAsyncCallsWithDistinctAsyncHandles()
|
||||
{
|
||||
// Arrange
|
||||
var runtime = new TestJSRuntime();
|
||||
|
||||
// Act
|
||||
runtime.InvokeAsync<object>("test identifier 1", "arg1", 123, true);
|
||||
runtime.InvokeAsync<object>("test identifier 2", "some other arg");
|
||||
|
||||
// Assert
|
||||
Assert.Collection(runtime.BeginInvokeCalls,
|
||||
call =>
|
||||
{
|
||||
Assert.Equal("test identifier 1", call.Identifier);
|
||||
Assert.Equal("[\"arg1\",123,true]", call.ArgsJson);
|
||||
},
|
||||
call =>
|
||||
{
|
||||
Assert.Equal("test identifier 2", call.Identifier);
|
||||
Assert.Equal("[\"some other arg\"]", call.ArgsJson);
|
||||
Assert.NotEqual(runtime.BeginInvokeCalls[0].AsyncHandle, call.AsyncHandle);
|
||||
});
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CanCompleteAsyncCallsAsSuccess()
|
||||
{
|
||||
// Arrange
|
||||
var runtime = new TestJSRuntime();
|
||||
|
||||
// Act/Assert: Tasks not initially completed
|
||||
var unrelatedTask = runtime.InvokeAsync<string>("unrelated call", Array.Empty<object>());
|
||||
var task = runtime.InvokeAsync<string>("test identifier", Array.Empty<object>());
|
||||
Assert.False(unrelatedTask.IsCompleted);
|
||||
Assert.False(task.IsCompleted);
|
||||
|
||||
// Act/Assert: Task can be completed
|
||||
runtime.OnEndInvoke(
|
||||
runtime.BeginInvokeCalls[1].AsyncHandle,
|
||||
/* succeeded: */ true,
|
||||
"my result");
|
||||
Assert.False(unrelatedTask.IsCompleted);
|
||||
Assert.True(task.IsCompleted);
|
||||
Assert.Equal("my result", task.Result);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CanCompleteAsyncCallsAsFailure()
|
||||
{
|
||||
// Arrange
|
||||
var runtime = new TestJSRuntime();
|
||||
|
||||
// Act/Assert: Tasks not initially completed
|
||||
var unrelatedTask = runtime.InvokeAsync<string>("unrelated call", Array.Empty<object>());
|
||||
var task = runtime.InvokeAsync<string>("test identifier", Array.Empty<object>());
|
||||
Assert.False(unrelatedTask.IsCompleted);
|
||||
Assert.False(task.IsCompleted);
|
||||
|
||||
// Act/Assert: Task can be failed
|
||||
runtime.OnEndInvoke(
|
||||
runtime.BeginInvokeCalls[1].AsyncHandle,
|
||||
/* succeeded: */ false,
|
||||
"This is a test exception");
|
||||
Assert.False(unrelatedTask.IsCompleted);
|
||||
Assert.True(task.IsCompleted);
|
||||
|
||||
Assert.IsType<AggregateException>(task.Exception);
|
||||
Assert.IsType<JSException>(task.Exception.InnerException);
|
||||
Assert.Equal("This is a test exception", ((JSException)task.Exception.InnerException).Message);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CannotCompleteSameAsyncCallMoreThanOnce()
|
||||
{
|
||||
// Arrange
|
||||
var runtime = new TestJSRuntime();
|
||||
|
||||
// Act/Assert
|
||||
runtime.InvokeAsync<string>("test identifier", Array.Empty<object>());
|
||||
var asyncHandle = runtime.BeginInvokeCalls[0].AsyncHandle;
|
||||
runtime.OnEndInvoke(asyncHandle, true, null);
|
||||
var ex = Assert.Throws<ArgumentException>(() =>
|
||||
{
|
||||
// Second "end invoke" will fail
|
||||
runtime.OnEndInvoke(asyncHandle, true, null);
|
||||
});
|
||||
Assert.Equal($"There is no pending task with handle '{asyncHandle}'.", ex.Message);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void SerializesDotNetObjectWrappersInKnownFormat()
|
||||
{
|
||||
// Arrange
|
||||
var runtime = new TestJSRuntime();
|
||||
var obj1 = new object();
|
||||
var obj2 = new object();
|
||||
var obj3 = new object();
|
||||
|
||||
// Act
|
||||
// Showing we can pass the DotNetObject either as top-level args or nested
|
||||
var obj1Ref = new DotNetObjectRef(obj1);
|
||||
var obj1DifferentRef = new DotNetObjectRef(obj1);
|
||||
runtime.InvokeAsync<object>("test identifier",
|
||||
obj1Ref,
|
||||
new Dictionary<string, object>
|
||||
{
|
||||
{ "obj2", new DotNetObjectRef(obj2) },
|
||||
{ "obj3", new DotNetObjectRef(obj3) },
|
||||
{ "obj1SameRef", obj1Ref },
|
||||
{ "obj1DifferentRef", obj1DifferentRef },
|
||||
});
|
||||
|
||||
// Assert: Serialized as expected
|
||||
var call = runtime.BeginInvokeCalls.Single();
|
||||
Assert.Equal("test identifier", call.Identifier);
|
||||
Assert.Equal("[\"__dotNetObject:1\",{\"obj2\":\"__dotNetObject:2\",\"obj3\":\"__dotNetObject:3\",\"obj1SameRef\":\"__dotNetObject:1\",\"obj1DifferentRef\":\"__dotNetObject:4\"}]", call.ArgsJson);
|
||||
|
||||
// Assert: Objects were tracked
|
||||
Assert.Same(obj1, runtime.ArgSerializerStrategy.FindDotNetObject(1));
|
||||
Assert.Same(obj2, runtime.ArgSerializerStrategy.FindDotNetObject(2));
|
||||
Assert.Same(obj3, runtime.ArgSerializerStrategy.FindDotNetObject(3));
|
||||
Assert.Same(obj1, runtime.ArgSerializerStrategy.FindDotNetObject(4));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void SupportsCustomSerializationForArguments()
|
||||
{
|
||||
// Arrange
|
||||
var runtime = new TestJSRuntime();
|
||||
|
||||
// Arrange/Act
|
||||
runtime.InvokeAsync<object>("test identifier",
|
||||
new WithCustomArgSerializer());
|
||||
|
||||
// Asssert
|
||||
var call = runtime.BeginInvokeCalls.Single();
|
||||
Assert.Equal("[{\"key1\":\"value1\",\"key2\":123}]", call.ArgsJson);
|
||||
}
|
||||
|
||||
class TestJSRuntime : JSRuntimeBase
|
||||
{
|
||||
public List<BeginInvokeAsyncArgs> BeginInvokeCalls = new List<BeginInvokeAsyncArgs>();
|
||||
|
||||
public class BeginInvokeAsyncArgs
|
||||
{
|
||||
public long AsyncHandle { get; set; }
|
||||
public string Identifier { get; set; }
|
||||
public string ArgsJson { get; set; }
|
||||
}
|
||||
|
||||
protected override void BeginInvokeJS(long asyncHandle, string identifier, string argsJson)
|
||||
{
|
||||
BeginInvokeCalls.Add(new BeginInvokeAsyncArgs
|
||||
{
|
||||
AsyncHandle = asyncHandle,
|
||||
Identifier = identifier,
|
||||
ArgsJson = argsJson,
|
||||
});
|
||||
}
|
||||
|
||||
public void OnEndInvoke(long asyncHandle, bool succeeded, object resultOrException)
|
||||
=> EndInvokeJS(asyncHandle, succeeded, resultOrException);
|
||||
}
|
||||
|
||||
class WithCustomArgSerializer : ICustomArgSerializer
|
||||
{
|
||||
public object ToJsonPrimitive()
|
||||
{
|
||||
return new Dictionary<string, object>
|
||||
{
|
||||
{ "key1", "value1" },
|
||||
{ "key2", 123 },
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,37 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Xunit;
|
||||
|
||||
namespace Microsoft.JSInterop.Tests
|
||||
{
|
||||
public class JSRuntimeTest
|
||||
{
|
||||
[Fact]
|
||||
public async Task CanHaveDistinctJSRuntimeInstancesInEachAsyncContext()
|
||||
{
|
||||
var tasks = Enumerable.Range(0, 20).Select(async _ =>
|
||||
{
|
||||
var jsRuntime = new FakeJSRuntime();
|
||||
JSRuntime.SetCurrentJSRuntime(jsRuntime);
|
||||
await Task.Delay(50).ConfigureAwait(false);
|
||||
Assert.Same(jsRuntime, JSRuntime.Current);
|
||||
});
|
||||
|
||||
await Task.WhenAll(tasks);
|
||||
Assert.Null(JSRuntime.Current);
|
||||
}
|
||||
|
||||
private class FakeJSRuntime : IJSRuntime
|
||||
{
|
||||
public Task<T> InvokeAsync<T>(string identifier, params object[] args)
|
||||
=> throw new NotImplementedException();
|
||||
|
||||
public void UntrackObjectRef(DotNetObjectRef dotNetObjectRef)
|
||||
=> throw new NotImplementedException();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,349 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using Microsoft.JSInterop.Internal;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Xunit;
|
||||
|
||||
namespace Microsoft.JSInterop.Tests
|
||||
{
|
||||
public class JsonUtilTest
|
||||
{
|
||||
// It's not useful to have a complete set of behavior specifications for
|
||||
// what the JSON serializer/deserializer does in all cases here. We merely
|
||||
// expose a simple wrapper over a third-party library that maintains its
|
||||
// own specs and tests.
|
||||
//
|
||||
// We should only add tests here to cover behaviors that Blazor itself
|
||||
// depends on.
|
||||
|
||||
[Theory]
|
||||
[InlineData(null, "null")]
|
||||
[InlineData("My string", "\"My string\"")]
|
||||
[InlineData(123, "123")]
|
||||
[InlineData(123.456f, "123.456")]
|
||||
[InlineData(123.456d, "123.456")]
|
||||
[InlineData(true, "true")]
|
||||
public void CanSerializePrimitivesToJson(object value, string expectedJson)
|
||||
{
|
||||
Assert.Equal(expectedJson, Json.Serialize(value));
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData("null", null)]
|
||||
[InlineData("\"My string\"", "My string")]
|
||||
[InlineData("123", 123L)] // Would also accept 123 as a System.Int32, but Int64 is fine as a default
|
||||
[InlineData("123.456", 123.456d)]
|
||||
[InlineData("true", true)]
|
||||
public void CanDeserializePrimitivesFromJson(string json, object expectedValue)
|
||||
{
|
||||
Assert.Equal(expectedValue, Json.Deserialize<object>(json));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CanSerializeClassToJson()
|
||||
{
|
||||
// Arrange
|
||||
var person = new Person
|
||||
{
|
||||
Id = 1844,
|
||||
Name = "Athos",
|
||||
Pets = new[] { "Aramis", "Porthos", "D'Artagnan" },
|
||||
Hobby = Hobbies.Swordfighting,
|
||||
SecondaryHobby = Hobbies.Reading,
|
||||
Nicknames = new List<string> { "Comte de la Fère", "Armand" },
|
||||
BirthInstant = new DateTimeOffset(1825, 8, 6, 18, 45, 21, TimeSpan.FromHours(-6)),
|
||||
Age = new TimeSpan(7665, 1, 30, 0),
|
||||
Allergies = new Dictionary<string, object> { { "Ducks", true }, { "Geese", false } },
|
||||
};
|
||||
|
||||
// Act/Assert
|
||||
Assert.Equal(
|
||||
"{\"id\":1844,\"name\":\"Athos\",\"pets\":[\"Aramis\",\"Porthos\",\"D'Artagnan\"],\"hobby\":2,\"secondaryHobby\":1,\"nullHobby\":null,\"nicknames\":[\"Comte de la Fère\",\"Armand\"],\"birthInstant\":\"1825-08-06T18:45:21.0000000-06:00\",\"age\":\"7665.01:30:00\",\"allergies\":{\"Ducks\":true,\"Geese\":false}}",
|
||||
Json.Serialize(person));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CanDeserializeClassFromJson()
|
||||
{
|
||||
// Arrange
|
||||
var json = "{\"id\":1844,\"name\":\"Athos\",\"pets\":[\"Aramis\",\"Porthos\",\"D'Artagnan\"],\"hobby\":2,\"secondaryHobby\":1,\"nullHobby\":null,\"nicknames\":[\"Comte de la Fère\",\"Armand\"],\"birthInstant\":\"1825-08-06T18:45:21.0000000-06:00\",\"age\":\"7665.01:30:00\",\"allergies\":{\"Ducks\":true,\"Geese\":false}}";
|
||||
|
||||
// Act
|
||||
var person = Json.Deserialize<Person>(json);
|
||||
|
||||
// Assert
|
||||
Assert.Equal(1844, person.Id);
|
||||
Assert.Equal("Athos", person.Name);
|
||||
Assert.Equal(new[] { "Aramis", "Porthos", "D'Artagnan" }, person.Pets);
|
||||
Assert.Equal(Hobbies.Swordfighting, person.Hobby);
|
||||
Assert.Equal(Hobbies.Reading, person.SecondaryHobby);
|
||||
Assert.Null(person.NullHobby);
|
||||
Assert.Equal(new[] { "Comte de la Fère", "Armand" }, person.Nicknames);
|
||||
Assert.Equal(new DateTimeOffset(1825, 8, 6, 18, 45, 21, TimeSpan.FromHours(-6)), person.BirthInstant);
|
||||
Assert.Equal(new TimeSpan(7665, 1, 30, 0), person.Age);
|
||||
Assert.Equal(new Dictionary<string, object> { { "Ducks", true }, { "Geese", false } }, person.Allergies);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CanDeserializeWithCaseInsensitiveKeys()
|
||||
{
|
||||
// Arrange
|
||||
var json = "{\"ID\":1844,\"NamE\":\"Athos\"}";
|
||||
|
||||
// Act
|
||||
var person = Json.Deserialize<Person>(json);
|
||||
|
||||
// Assert
|
||||
Assert.Equal(1844, person.Id);
|
||||
Assert.Equal("Athos", person.Name);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void DeserializationPrefersPropertiesOverFields()
|
||||
{
|
||||
// Arrange
|
||||
var json = "{\"member1\":\"Hello\"}";
|
||||
|
||||
// Act
|
||||
var person = Json.Deserialize<PrefersPropertiesOverFields>(json);
|
||||
|
||||
// Assert
|
||||
Assert.Equal("Hello", person.Member1);
|
||||
Assert.Null(person.member1);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CanSerializeStructToJson()
|
||||
{
|
||||
// Arrange
|
||||
var commandResult = new SimpleStruct
|
||||
{
|
||||
StringProperty = "Test",
|
||||
BoolProperty = true,
|
||||
NullableIntProperty = 1
|
||||
};
|
||||
|
||||
// Act
|
||||
var result = Json.Serialize(commandResult);
|
||||
|
||||
// Assert
|
||||
Assert.Equal("{\"stringProperty\":\"Test\",\"boolProperty\":true,\"nullableIntProperty\":1}", result);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CanDeserializeStructFromJson()
|
||||
{
|
||||
// Arrange
|
||||
var json = "{\"stringProperty\":\"Test\",\"boolProperty\":true,\"nullableIntProperty\":1}";
|
||||
|
||||
//Act
|
||||
var simpleError = Json.Deserialize<SimpleStruct>(json);
|
||||
|
||||
// Assert
|
||||
Assert.Equal("Test", simpleError.StringProperty);
|
||||
Assert.True(simpleError.BoolProperty);
|
||||
Assert.Equal(1, simpleError.NullableIntProperty);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CanCreateInstanceOfClassWithPrivateConstructor()
|
||||
{
|
||||
// Arrange
|
||||
var expectedName = "NameValue";
|
||||
var json = $"{{\"Name\":\"{expectedName}\"}}";
|
||||
|
||||
// Act
|
||||
var instance = Json.Deserialize<PrivateConstructor>(json);
|
||||
|
||||
// Assert
|
||||
Assert.Equal(expectedName, instance.Name);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CanSetValueOfPublicPropertiesWithNonPublicSetters()
|
||||
{
|
||||
// Arrange
|
||||
var expectedPrivateValue = "PrivateValue";
|
||||
var expectedProtectedValue = "ProtectedValue";
|
||||
var expectedInternalValue = "InternalValue";
|
||||
|
||||
var json = "{" +
|
||||
$"\"PrivateSetter\":\"{expectedPrivateValue}\"," +
|
||||
$"\"ProtectedSetter\":\"{expectedProtectedValue}\"," +
|
||||
$"\"InternalSetter\":\"{expectedInternalValue}\"," +
|
||||
"}";
|
||||
|
||||
// Act
|
||||
var instance = Json.Deserialize<NonPublicSetterOnPublicProperty>(json);
|
||||
|
||||
// Assert
|
||||
Assert.Equal(expectedPrivateValue, instance.PrivateSetter);
|
||||
Assert.Equal(expectedProtectedValue, instance.ProtectedSetter);
|
||||
Assert.Equal(expectedInternalValue, instance.InternalSetter);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void RejectsTypesWithAmbiguouslyNamedProperties()
|
||||
{
|
||||
var ex = Assert.Throws<InvalidOperationException>(() =>
|
||||
{
|
||||
Json.Deserialize<ClashingProperties>("{}");
|
||||
});
|
||||
|
||||
Assert.Equal($"The type '{typeof(ClashingProperties).FullName}' contains multiple public properties " +
|
||||
$"with names case-insensitively matching '{nameof(ClashingProperties.PROP1).ToLowerInvariant()}'. " +
|
||||
$"Such types cannot be used for JSON deserialization.",
|
||||
ex.Message);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void RejectsTypesWithAmbiguouslyNamedFields()
|
||||
{
|
||||
var ex = Assert.Throws<InvalidOperationException>(() =>
|
||||
{
|
||||
Json.Deserialize<ClashingFields>("{}");
|
||||
});
|
||||
|
||||
Assert.Equal($"The type '{typeof(ClashingFields).FullName}' contains multiple public fields " +
|
||||
$"with names case-insensitively matching '{nameof(ClashingFields.Field1).ToLowerInvariant()}'. " +
|
||||
$"Such types cannot be used for JSON deserialization.",
|
||||
ex.Message);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void NonEmptyConstructorThrowsUsefulException()
|
||||
{
|
||||
// Arrange
|
||||
var json = "{\"Property\":1}";
|
||||
var type = typeof(NonEmptyConstructorPoco);
|
||||
|
||||
// Act
|
||||
var exception = Assert.Throws<InvalidOperationException>(() =>
|
||||
{
|
||||
Json.Deserialize<NonEmptyConstructorPoco>(json);
|
||||
});
|
||||
|
||||
// Assert
|
||||
Assert.Equal(
|
||||
$"Cannot deserialize JSON into type '{type.FullName}' because it does not have a public parameterless constructor.",
|
||||
exception.Message);
|
||||
}
|
||||
|
||||
// Test cases based on https://github.com/JamesNK/Newtonsoft.Json/blob/122afba9908832bd5ac207164ee6c303bfd65cf1/Src/Newtonsoft.Json.Tests/Utilities/StringUtilsTests.cs#L41
|
||||
// The only difference is that our logic doesn't have to handle space-separated words,
|
||||
// because we're only use this for camelcasing .NET member names
|
||||
//
|
||||
// Not all of the following cases are really valid .NET member names, but we have no reason
|
||||
// to implement more logic to detect invalid member names besides the basics (null or empty).
|
||||
[Theory]
|
||||
[InlineData("URLValue", "urlValue")]
|
||||
[InlineData("URL", "url")]
|
||||
[InlineData("ID", "id")]
|
||||
[InlineData("I", "i")]
|
||||
[InlineData("Person", "person")]
|
||||
[InlineData("xPhone", "xPhone")]
|
||||
[InlineData("XPhone", "xPhone")]
|
||||
[InlineData("X_Phone", "x_Phone")]
|
||||
[InlineData("X__Phone", "x__Phone")]
|
||||
[InlineData("IsCIA", "isCIA")]
|
||||
[InlineData("VmQ", "vmQ")]
|
||||
[InlineData("Xml2Json", "xml2Json")]
|
||||
[InlineData("SnAkEcAsE", "snAkEcAsE")]
|
||||
[InlineData("SnA__kEcAsE", "snA__kEcAsE")]
|
||||
[InlineData("already_snake_case_", "already_snake_case_")]
|
||||
[InlineData("IsJSONProperty", "isJSONProperty")]
|
||||
[InlineData("SHOUTING_CASE", "shoutinG_CASE")]
|
||||
[InlineData("9999-12-31T23:59:59.9999999Z", "9999-12-31T23:59:59.9999999Z")]
|
||||
[InlineData("Hi!! This is text. Time to test.", "hi!! This is text. Time to test.")]
|
||||
[InlineData("BUILDING", "building")]
|
||||
[InlineData("BUILDINGProperty", "buildingProperty")]
|
||||
public void MemberNameToCamelCase_Valid(string input, string expectedOutput)
|
||||
{
|
||||
Assert.Equal(expectedOutput, CamelCase.MemberNameToCamelCase(input));
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData("")]
|
||||
[InlineData(null)]
|
||||
public void MemberNameToCamelCase_Invalid(string input)
|
||||
{
|
||||
var ex = Assert.Throws<ArgumentException>(() =>
|
||||
CamelCase.MemberNameToCamelCase(input));
|
||||
Assert.Equal("value", ex.ParamName);
|
||||
Assert.StartsWith($"The value '{input ?? "null"}' is not a valid member name.", ex.Message);
|
||||
}
|
||||
|
||||
class NonEmptyConstructorPoco
|
||||
{
|
||||
public NonEmptyConstructorPoco(int parameter) { }
|
||||
|
||||
public int Property { get; set; }
|
||||
}
|
||||
|
||||
struct SimpleStruct
|
||||
{
|
||||
public string StringProperty { get; set; }
|
||||
public bool BoolProperty { get; set; }
|
||||
public int? NullableIntProperty { get; set; }
|
||||
}
|
||||
|
||||
class Person
|
||||
{
|
||||
public int Id { get; set; }
|
||||
public string Name { get; set; }
|
||||
public string[] Pets { get; set; }
|
||||
public Hobbies Hobby { get; set; }
|
||||
public Hobbies? SecondaryHobby { get; set; }
|
||||
public Hobbies? NullHobby { get; set; }
|
||||
public IList<string> Nicknames { get; set; }
|
||||
public DateTimeOffset BirthInstant { get; set; }
|
||||
public TimeSpan Age { get; set; }
|
||||
public IDictionary<string, object> Allergies { get; set; }
|
||||
}
|
||||
|
||||
enum Hobbies { Reading = 1, Swordfighting = 2 }
|
||||
|
||||
#pragma warning disable 0649
|
||||
class ClashingProperties
|
||||
{
|
||||
public string Prop1 { get; set; }
|
||||
public int PROP1 { get; set; }
|
||||
}
|
||||
|
||||
class ClashingFields
|
||||
{
|
||||
public string Field1;
|
||||
public int field1;
|
||||
}
|
||||
|
||||
class PrefersPropertiesOverFields
|
||||
{
|
||||
public string member1;
|
||||
public string Member1 { get; set; }
|
||||
}
|
||||
#pragma warning restore 0649
|
||||
|
||||
class PrivateConstructor
|
||||
{
|
||||
public string Name { get; set; }
|
||||
|
||||
private PrivateConstructor()
|
||||
{
|
||||
}
|
||||
|
||||
public PrivateConstructor(string name)
|
||||
{
|
||||
Name = name;
|
||||
}
|
||||
}
|
||||
|
||||
class NonPublicSetterOnPublicProperty
|
||||
{
|
||||
public string PrivateSetter { get; private set; }
|
||||
public string ProtectedSetter { get; protected set; }
|
||||
public string InternalSetter { get; internal set; }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,15 @@
|
|||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>netcoreapp3.0</TargetFramework>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="../src/Microsoft.JSInterop.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<None Update="xunit.runner.json" CopyToOutputDirectory="PreserveNewest" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
{
|
||||
"shadowCopy": true
|
||||
}
|
||||
|
|
@ -0,0 +1,10 @@
|
|||
<!-- This file is automatically generated. -->
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<PropertyGroup>
|
||||
<TargetFrameworks>netstandard2.0</TargetFrameworks>
|
||||
</PropertyGroup>
|
||||
<ItemGroup Condition="'$(TargetFramework)' == 'netstandard2.0'">
|
||||
<Compile Include="Mono.WebAssembly.Interop.netstandard2.0.cs" />
|
||||
<Reference Include="Microsoft.JSInterop" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
|
|
@ -0,0 +1,16 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
namespace Mono.WebAssembly.Interop
|
||||
{
|
||||
public partial class MonoWebAssemblyJSRuntime : Microsoft.JSInterop.JSInProcessRuntimeBase
|
||||
{
|
||||
public MonoWebAssemblyJSRuntime() { }
|
||||
protected override void BeginInvokeJS(long asyncHandle, string identifier, string argsJson) { }
|
||||
protected override string InvokeJS(string identifier, string argsJson) { throw null; }
|
||||
public TRes InvokeUnmarshalled<TRes>(string identifier) { throw null; }
|
||||
public TRes InvokeUnmarshalled<T0, TRes>(string identifier, T0 arg0) { throw null; }
|
||||
public TRes InvokeUnmarshalled<T0, T1, TRes>(string identifier, T0 arg0, T1 arg1) { throw null; }
|
||||
public TRes InvokeUnmarshalled<T0, T1, T2, TRes>(string identifier, T0 arg0, T1 arg1, T2 arg2) { throw null; }
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,25 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
namespace WebAssembly.JSInterop
|
||||
{
|
||||
/// <summary>
|
||||
/// Methods that map to the functions compiled into the Mono WebAssembly runtime,
|
||||
/// as defined by 'mono_add_internal_call' calls in driver.c
|
||||
/// </summary>
|
||||
internal class InternalCalls
|
||||
{
|
||||
// The exact namespace, type, and method names must match the corresponding entries
|
||||
// in driver.c in the Mono distribution
|
||||
|
||||
// We're passing asyncHandle by ref not because we want it to be writable, but so it gets
|
||||
// passed as a pointer (4 bytes). We can pass 4-byte values, but not 8-byte ones.
|
||||
[MethodImpl(MethodImplOptions.InternalCall)]
|
||||
public static extern string InvokeJSMarshalled(out string exception, ref long asyncHandle, string functionIdentifier, string argsJson);
|
||||
|
||||
[MethodImpl(MethodImplOptions.InternalCall)]
|
||||
public static extern TRes InvokeJSUnmarshalled<T0, T1, T2, TRes>(out string exception, string functionIdentifier, T0 arg0, T1 arg1, T2 arg2);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,15 @@
|
|||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>netstandard2.0</TargetFramework>
|
||||
<Description>Abstractions and features for interop between Mono WebAssembly and JavaScript code.</Description>
|
||||
<PackageTags>wasm;javascript;interop</PackageTags>
|
||||
<GenerateDocumentationFile>true</GenerateDocumentationFile>
|
||||
<IsShipping>true</IsShipping>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Reference Include="Microsoft.JSInterop" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
|
@ -0,0 +1,114 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using Microsoft.JSInterop;
|
||||
using WebAssembly.JSInterop;
|
||||
|
||||
namespace Mono.WebAssembly.Interop
|
||||
{
|
||||
/// <summary>
|
||||
/// Provides methods for invoking JavaScript functions for applications running
|
||||
/// on the Mono WebAssembly runtime.
|
||||
/// </summary>
|
||||
public class MonoWebAssemblyJSRuntime : JSInProcessRuntimeBase
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override string InvokeJS(string identifier, string argsJson)
|
||||
{
|
||||
var noAsyncHandle = default(long);
|
||||
var result = InternalCalls.InvokeJSMarshalled(out var exception, ref noAsyncHandle, identifier, argsJson);
|
||||
return exception != null
|
||||
? throw new JSException(exception)
|
||||
: result;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void BeginInvokeJS(long asyncHandle, string identifier, string argsJson)
|
||||
{
|
||||
InternalCalls.InvokeJSMarshalled(out _, ref asyncHandle, identifier, argsJson);
|
||||
}
|
||||
|
||||
// Invoked via Mono's JS interop mechanism (invoke_method)
|
||||
private static string InvokeDotNet(string assemblyName, string methodIdentifier, string dotNetObjectId, string argsJson)
|
||||
=> DotNetDispatcher.Invoke(assemblyName, methodIdentifier, dotNetObjectId == null ? default : long.Parse(dotNetObjectId), argsJson);
|
||||
|
||||
// Invoked via Mono's JS interop mechanism (invoke_method)
|
||||
private static void BeginInvokeDotNet(string callId, string assemblyNameOrDotNetObjectId, string methodIdentifier, string argsJson)
|
||||
{
|
||||
// Figure out whether 'assemblyNameOrDotNetObjectId' is the assembly name or the instance ID
|
||||
// We only need one for any given call. This helps to work around the limitation that we can
|
||||
// only pass a maximum of 4 args in a call from JS to Mono WebAssembly.
|
||||
string assemblyName;
|
||||
long dotNetObjectId;
|
||||
if (char.IsDigit(assemblyNameOrDotNetObjectId[0]))
|
||||
{
|
||||
dotNetObjectId = long.Parse(assemblyNameOrDotNetObjectId);
|
||||
assemblyName = null;
|
||||
}
|
||||
else
|
||||
{
|
||||
dotNetObjectId = default;
|
||||
assemblyName = assemblyNameOrDotNetObjectId;
|
||||
}
|
||||
|
||||
DotNetDispatcher.BeginInvoke(callId, assemblyName, methodIdentifier, dotNetObjectId, argsJson);
|
||||
}
|
||||
|
||||
#region Custom MonoWebAssemblyJSRuntime methods
|
||||
|
||||
/// <summary>
|
||||
/// Invokes the JavaScript function registered with the specified identifier.
|
||||
/// </summary>
|
||||
/// <typeparam name="TRes">The .NET type corresponding to the function's return value type.</typeparam>
|
||||
/// <param name="identifier">The identifier used when registering the target function.</param>
|
||||
/// <returns>The result of the function invocation.</returns>
|
||||
public TRes InvokeUnmarshalled<TRes>(string identifier)
|
||||
=> InvokeUnmarshalled<object, object, object, TRes>(identifier, null, null, null);
|
||||
|
||||
/// <summary>
|
||||
/// Invokes the JavaScript function registered with the specified identifier.
|
||||
/// </summary>
|
||||
/// <typeparam name="T0">The type of the first argument.</typeparam>
|
||||
/// <typeparam name="TRes">The .NET type corresponding to the function's return value type.</typeparam>
|
||||
/// <param name="identifier">The identifier used when registering the target function.</param>
|
||||
/// <param name="arg0">The first argument.</param>
|
||||
/// <returns>The result of the function invocation.</returns>
|
||||
public TRes InvokeUnmarshalled<T0, TRes>(string identifier, T0 arg0)
|
||||
=> InvokeUnmarshalled<T0, object, object, TRes>(identifier, arg0, null, null);
|
||||
|
||||
/// <summary>
|
||||
/// Invokes the JavaScript function registered with the specified identifier.
|
||||
/// </summary>
|
||||
/// <typeparam name="T0">The type of the first argument.</typeparam>
|
||||
/// <typeparam name="T1">The type of the second argument.</typeparam>
|
||||
/// <typeparam name="TRes">The .NET type corresponding to the function's return value type.</typeparam>
|
||||
/// <param name="identifier">The identifier used when registering the target function.</param>
|
||||
/// <param name="arg0">The first argument.</param>
|
||||
/// <param name="arg1">The second argument.</param>
|
||||
/// <returns>The result of the function invocation.</returns>
|
||||
public TRes InvokeUnmarshalled<T0, T1, TRes>(string identifier, T0 arg0, T1 arg1)
|
||||
=> InvokeUnmarshalled<T0, T1, object, TRes>(identifier, arg0, arg1, null);
|
||||
|
||||
/// <summary>
|
||||
/// Invokes the JavaScript function registered with the specified identifier.
|
||||
/// </summary>
|
||||
/// <typeparam name="T0">The type of the first argument.</typeparam>
|
||||
/// <typeparam name="T1">The type of the second argument.</typeparam>
|
||||
/// <typeparam name="T2">The type of the third argument.</typeparam>
|
||||
/// <typeparam name="TRes">The .NET type corresponding to the function's return value type.</typeparam>
|
||||
/// <param name="identifier">The identifier used when registering the target function.</param>
|
||||
/// <param name="arg0">The first argument.</param>
|
||||
/// <param name="arg1">The second argument.</param>
|
||||
/// <param name="arg2">The third argument.</param>
|
||||
/// <returns>The result of the function invocation.</returns>
|
||||
public TRes InvokeUnmarshalled<T0, T1, T2, TRes>(string identifier, T0 arg0, T1 arg1, T2 arg2)
|
||||
{
|
||||
var result = InternalCalls.InvokeJSUnmarshalled<T0, T1, T2, TRes>(out var exception, identifier, arg0, arg1, arg2);
|
||||
return exception != null
|
||||
? throw new JSException(exception)
|
||||
: result;
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,8 @@
|
|||
# jsinterop
|
||||
|
||||
This repo is for `Microsoft.JSInterop`, a package that provides abstractions and features for interop between .NET and JavaScript code.
|
||||
|
||||
## Usage
|
||||
|
||||
The primary use case is for applications built with Mono WebAssembly or Blazor. It's not expected that developers will typically use these libraries separately from Mono WebAssembly, Blazor, or a similar technology.
|
||||
|
||||
|
|
@ -0,0 +1,10 @@
|
|||
<!-- This file is automatically generated. -->
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<PropertyGroup>
|
||||
<TargetFrameworks>netstandard2.0</TargetFrameworks>
|
||||
</PropertyGroup>
|
||||
<ItemGroup Condition="'$(TargetFramework)' == 'netstandard2.0'">
|
||||
<Compile Include="Microsoft.Extensions.Localization.Abstractions.netstandard2.0.cs" />
|
||||
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
|
|
@ -0,0 +1,49 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
namespace Microsoft.Extensions.Localization
|
||||
{
|
||||
public partial interface IStringLocalizer
|
||||
{
|
||||
Microsoft.Extensions.Localization.LocalizedString this[string name] { get; }
|
||||
Microsoft.Extensions.Localization.LocalizedString this[string name, params object[] arguments] { get; }
|
||||
System.Collections.Generic.IEnumerable<Microsoft.Extensions.Localization.LocalizedString> GetAllStrings(bool includeParentCultures);
|
||||
[System.ObsoleteAttribute("This method is obsolete. Use `CurrentCulture` and `CurrentUICulture` instead.")]
|
||||
Microsoft.Extensions.Localization.IStringLocalizer WithCulture(System.Globalization.CultureInfo culture);
|
||||
}
|
||||
public partial interface IStringLocalizerFactory
|
||||
{
|
||||
Microsoft.Extensions.Localization.IStringLocalizer Create(string baseName, string location);
|
||||
Microsoft.Extensions.Localization.IStringLocalizer Create(System.Type resourceSource);
|
||||
}
|
||||
public partial interface IStringLocalizer<out T> : Microsoft.Extensions.Localization.IStringLocalizer
|
||||
{
|
||||
}
|
||||
public partial class LocalizedString
|
||||
{
|
||||
public LocalizedString(string name, string value) { }
|
||||
public LocalizedString(string name, string value, bool resourceNotFound) { }
|
||||
public LocalizedString(string name, string value, bool resourceNotFound, string searchedLocation) { }
|
||||
public string Name { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } }
|
||||
public bool ResourceNotFound { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } }
|
||||
public string SearchedLocation { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } }
|
||||
public string Value { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } }
|
||||
public static implicit operator string (Microsoft.Extensions.Localization.LocalizedString localizedString) { throw null; }
|
||||
public override string ToString() { throw null; }
|
||||
}
|
||||
public static partial class StringLocalizerExtensions
|
||||
{
|
||||
public static System.Collections.Generic.IEnumerable<Microsoft.Extensions.Localization.LocalizedString> GetAllStrings(this Microsoft.Extensions.Localization.IStringLocalizer stringLocalizer) { throw null; }
|
||||
public static Microsoft.Extensions.Localization.LocalizedString GetString(this Microsoft.Extensions.Localization.IStringLocalizer stringLocalizer, string name) { throw null; }
|
||||
public static Microsoft.Extensions.Localization.LocalizedString GetString(this Microsoft.Extensions.Localization.IStringLocalizer stringLocalizer, string name, params object[] arguments) { throw null; }
|
||||
}
|
||||
public partial class StringLocalizer<TResourceSource> : Microsoft.Extensions.Localization.IStringLocalizer, Microsoft.Extensions.Localization.IStringLocalizer<TResourceSource>
|
||||
{
|
||||
public StringLocalizer(Microsoft.Extensions.Localization.IStringLocalizerFactory factory) { }
|
||||
public virtual Microsoft.Extensions.Localization.LocalizedString this[string name] { get { throw null; } }
|
||||
public virtual Microsoft.Extensions.Localization.LocalizedString this[string name, params object[] arguments] { get { throw null; } }
|
||||
public System.Collections.Generic.IEnumerable<Microsoft.Extensions.Localization.LocalizedString> GetAllStrings(bool includeParentCultures) { throw null; }
|
||||
[System.ObsoleteAttribute("This method is obsolete. Use `CurrentCulture` and `CurrentUICulture` instead.")]
|
||||
public virtual Microsoft.Extensions.Localization.IStringLocalizer WithCulture(System.Globalization.CultureInfo culture) { throw null; }
|
||||
}
|
||||
}
|
||||
|
|
@ -1,6 +1,7 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
|
||||
|
|
@ -40,6 +41,7 @@ namespace Microsoft.Extensions.Localization
|
|||
/// </summary>
|
||||
/// <param name="culture">The <see cref="CultureInfo"/> to use.</param>
|
||||
/// <returns>A culture-specific <see cref="IStringLocalizer"/>.</returns>
|
||||
[Obsolete("This method is obsolete. Use `CurrentCulture` and `CurrentUICulture` instead.")]
|
||||
IStringLocalizer WithCulture(CultureInfo culture);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -7,8 +7,8 @@ namespace Microsoft.Extensions.Localization
|
|||
/// Represents an <see cref="IStringLocalizer"/> that provides strings for <typeparamref name="T"/>.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The <see cref="System.Type"/> to provide strings for.</typeparam>
|
||||
public interface IStringLocalizer<T> : IStringLocalizer
|
||||
public interface IStringLocalizer<out T> : IStringLocalizer
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@ Microsoft.Extensions.Localization.IStringLocalizer<T></Description>
|
|||
<NoWarn>$(NoWarn);CS1591</NoWarn>
|
||||
<GenerateDocumentationFile>true</GenerateDocumentationFile>
|
||||
<PackageTags>localization</PackageTags>
|
||||
<IsShipping>true</IsShipping>
|
||||
</PropertyGroup>
|
||||
|
||||
</Project>
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
|
@ -30,6 +30,7 @@ namespace Microsoft.Extensions.Localization
|
|||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
[Obsolete("This method is obsolete. Use `CurrentCulture` and `CurrentUICulture` instead.")]
|
||||
public virtual IStringLocalizer WithCulture(CultureInfo culture) => _localizer.WithCulture(culture);
|
||||
|
||||
/// <inheritdoc />
|
||||
|
|
@ -64,4 +65,4 @@ namespace Microsoft.Extensions.Localization
|
|||
public IEnumerable<LocalizedString> GetAllStrings(bool includeParentCultures) =>
|
||||
_localizer.GetAllStrings(includeParentCultures);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,413 +0,0 @@
|
|||
{
|
||||
"AssemblyIdentity": "Microsoft.Extensions.Localization.Abstractions, Version=2.1.1.0, Culture=neutral, PublicKeyToken=adb9793829ddae60",
|
||||
"Types": [
|
||||
{
|
||||
"Name": "Microsoft.Extensions.Localization.IStringLocalizer",
|
||||
"Visibility": "Public",
|
||||
"Kind": "Interface",
|
||||
"Abstract": true,
|
||||
"ImplementedInterfaces": [],
|
||||
"Members": [
|
||||
{
|
||||
"Kind": "Method",
|
||||
"Name": "get_Item",
|
||||
"Parameters": [
|
||||
{
|
||||
"Name": "name",
|
||||
"Type": "System.String"
|
||||
}
|
||||
],
|
||||
"ReturnType": "Microsoft.Extensions.Localization.LocalizedString",
|
||||
"GenericParameter": []
|
||||
},
|
||||
{
|
||||
"Kind": "Method",
|
||||
"Name": "get_Item",
|
||||
"Parameters": [
|
||||
{
|
||||
"Name": "name",
|
||||
"Type": "System.String"
|
||||
},
|
||||
{
|
||||
"Name": "arguments",
|
||||
"Type": "System.Object[]",
|
||||
"IsParams": true
|
||||
}
|
||||
],
|
||||
"ReturnType": "Microsoft.Extensions.Localization.LocalizedString",
|
||||
"GenericParameter": []
|
||||
},
|
||||
{
|
||||
"Kind": "Method",
|
||||
"Name": "GetAllStrings",
|
||||
"Parameters": [
|
||||
{
|
||||
"Name": "includeParentCultures",
|
||||
"Type": "System.Boolean"
|
||||
}
|
||||
],
|
||||
"ReturnType": "System.Collections.Generic.IEnumerable<Microsoft.Extensions.Localization.LocalizedString>",
|
||||
"GenericParameter": []
|
||||
},
|
||||
{
|
||||
"Kind": "Method",
|
||||
"Name": "WithCulture",
|
||||
"Parameters": [
|
||||
{
|
||||
"Name": "culture",
|
||||
"Type": "System.Globalization.CultureInfo"
|
||||
}
|
||||
],
|
||||
"ReturnType": "Microsoft.Extensions.Localization.IStringLocalizer",
|
||||
"GenericParameter": []
|
||||
}
|
||||
],
|
||||
"GenericParameters": []
|
||||
},
|
||||
{
|
||||
"Name": "Microsoft.Extensions.Localization.IStringLocalizerFactory",
|
||||
"Visibility": "Public",
|
||||
"Kind": "Interface",
|
||||
"Abstract": true,
|
||||
"ImplementedInterfaces": [],
|
||||
"Members": [
|
||||
{
|
||||
"Kind": "Method",
|
||||
"Name": "Create",
|
||||
"Parameters": [
|
||||
{
|
||||
"Name": "resourceSource",
|
||||
"Type": "System.Type"
|
||||
}
|
||||
],
|
||||
"ReturnType": "Microsoft.Extensions.Localization.IStringLocalizer",
|
||||
"GenericParameter": []
|
||||
},
|
||||
{
|
||||
"Kind": "Method",
|
||||
"Name": "Create",
|
||||
"Parameters": [
|
||||
{
|
||||
"Name": "baseName",
|
||||
"Type": "System.String"
|
||||
},
|
||||
{
|
||||
"Name": "location",
|
||||
"Type": "System.String"
|
||||
}
|
||||
],
|
||||
"ReturnType": "Microsoft.Extensions.Localization.IStringLocalizer",
|
||||
"GenericParameter": []
|
||||
}
|
||||
],
|
||||
"GenericParameters": []
|
||||
},
|
||||
{
|
||||
"Name": "Microsoft.Extensions.Localization.IStringLocalizer<T0>",
|
||||
"Visibility": "Public",
|
||||
"Kind": "Interface",
|
||||
"Abstract": true,
|
||||
"ImplementedInterfaces": [
|
||||
"Microsoft.Extensions.Localization.IStringLocalizer"
|
||||
],
|
||||
"Members": [],
|
||||
"GenericParameters": [
|
||||
{
|
||||
"ParameterName": "T",
|
||||
"ParameterPosition": 0,
|
||||
"BaseTypeOrInterfaces": []
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"Name": "Microsoft.Extensions.Localization.LocalizedString",
|
||||
"Visibility": "Public",
|
||||
"Kind": "Class",
|
||||
"ImplementedInterfaces": [],
|
||||
"Members": [
|
||||
{
|
||||
"Kind": "Method",
|
||||
"Name": "op_Implicit",
|
||||
"Parameters": [
|
||||
{
|
||||
"Name": "localizedString",
|
||||
"Type": "Microsoft.Extensions.Localization.LocalizedString"
|
||||
}
|
||||
],
|
||||
"ReturnType": "System.String",
|
||||
"Static": true,
|
||||
"Visibility": "Public",
|
||||
"GenericParameter": []
|
||||
},
|
||||
{
|
||||
"Kind": "Method",
|
||||
"Name": "get_Name",
|
||||
"Parameters": [],
|
||||
"ReturnType": "System.String",
|
||||
"Visibility": "Public",
|
||||
"GenericParameter": []
|
||||
},
|
||||
{
|
||||
"Kind": "Method",
|
||||
"Name": "get_Value",
|
||||
"Parameters": [],
|
||||
"ReturnType": "System.String",
|
||||
"Visibility": "Public",
|
||||
"GenericParameter": []
|
||||
},
|
||||
{
|
||||
"Kind": "Method",
|
||||
"Name": "get_ResourceNotFound",
|
||||
"Parameters": [],
|
||||
"ReturnType": "System.Boolean",
|
||||
"Visibility": "Public",
|
||||
"GenericParameter": []
|
||||
},
|
||||
{
|
||||
"Kind": "Method",
|
||||
"Name": "get_SearchedLocation",
|
||||
"Parameters": [],
|
||||
"ReturnType": "System.String",
|
||||
"Visibility": "Public",
|
||||
"GenericParameter": []
|
||||
},
|
||||
{
|
||||
"Kind": "Method",
|
||||
"Name": "ToString",
|
||||
"Parameters": [],
|
||||
"ReturnType": "System.String",
|
||||
"Virtual": true,
|
||||
"Override": true,
|
||||
"Visibility": "Public",
|
||||
"GenericParameter": []
|
||||
},
|
||||
{
|
||||
"Kind": "Constructor",
|
||||
"Name": ".ctor",
|
||||
"Parameters": [
|
||||
{
|
||||
"Name": "name",
|
||||
"Type": "System.String"
|
||||
},
|
||||
{
|
||||
"Name": "value",
|
||||
"Type": "System.String"
|
||||
}
|
||||
],
|
||||
"Visibility": "Public",
|
||||
"GenericParameter": []
|
||||
},
|
||||
{
|
||||
"Kind": "Constructor",
|
||||
"Name": ".ctor",
|
||||
"Parameters": [
|
||||
{
|
||||
"Name": "name",
|
||||
"Type": "System.String"
|
||||
},
|
||||
{
|
||||
"Name": "value",
|
||||
"Type": "System.String"
|
||||
},
|
||||
{
|
||||
"Name": "resourceNotFound",
|
||||
"Type": "System.Boolean"
|
||||
}
|
||||
],
|
||||
"Visibility": "Public",
|
||||
"GenericParameter": []
|
||||
},
|
||||
{
|
||||
"Kind": "Constructor",
|
||||
"Name": ".ctor",
|
||||
"Parameters": [
|
||||
{
|
||||
"Name": "name",
|
||||
"Type": "System.String"
|
||||
},
|
||||
{
|
||||
"Name": "value",
|
||||
"Type": "System.String"
|
||||
},
|
||||
{
|
||||
"Name": "resourceNotFound",
|
||||
"Type": "System.Boolean"
|
||||
},
|
||||
{
|
||||
"Name": "searchedLocation",
|
||||
"Type": "System.String"
|
||||
}
|
||||
],
|
||||
"Visibility": "Public",
|
||||
"GenericParameter": []
|
||||
}
|
||||
],
|
||||
"GenericParameters": []
|
||||
},
|
||||
{
|
||||
"Name": "Microsoft.Extensions.Localization.StringLocalizerExtensions",
|
||||
"Visibility": "Public",
|
||||
"Kind": "Class",
|
||||
"Abstract": true,
|
||||
"Static": true,
|
||||
"Sealed": true,
|
||||
"ImplementedInterfaces": [],
|
||||
"Members": [
|
||||
{
|
||||
"Kind": "Method",
|
||||
"Name": "GetString",
|
||||
"Parameters": [
|
||||
{
|
||||
"Name": "stringLocalizer",
|
||||
"Type": "Microsoft.Extensions.Localization.IStringLocalizer"
|
||||
},
|
||||
{
|
||||
"Name": "name",
|
||||
"Type": "System.String"
|
||||
}
|
||||
],
|
||||
"ReturnType": "Microsoft.Extensions.Localization.LocalizedString",
|
||||
"Static": true,
|
||||
"Extension": true,
|
||||
"Visibility": "Public",
|
||||
"GenericParameter": []
|
||||
},
|
||||
{
|
||||
"Kind": "Method",
|
||||
"Name": "GetString",
|
||||
"Parameters": [
|
||||
{
|
||||
"Name": "stringLocalizer",
|
||||
"Type": "Microsoft.Extensions.Localization.IStringLocalizer"
|
||||
},
|
||||
{
|
||||
"Name": "name",
|
||||
"Type": "System.String"
|
||||
},
|
||||
{
|
||||
"Name": "arguments",
|
||||
"Type": "System.Object[]",
|
||||
"IsParams": true
|
||||
}
|
||||
],
|
||||
"ReturnType": "Microsoft.Extensions.Localization.LocalizedString",
|
||||
"Static": true,
|
||||
"Extension": true,
|
||||
"Visibility": "Public",
|
||||
"GenericParameter": []
|
||||
},
|
||||
{
|
||||
"Kind": "Method",
|
||||
"Name": "GetAllStrings",
|
||||
"Parameters": [
|
||||
{
|
||||
"Name": "stringLocalizer",
|
||||
"Type": "Microsoft.Extensions.Localization.IStringLocalizer"
|
||||
}
|
||||
],
|
||||
"ReturnType": "System.Collections.Generic.IEnumerable<Microsoft.Extensions.Localization.LocalizedString>",
|
||||
"Static": true,
|
||||
"Extension": true,
|
||||
"Visibility": "Public",
|
||||
"GenericParameter": []
|
||||
}
|
||||
],
|
||||
"GenericParameters": []
|
||||
},
|
||||
{
|
||||
"Name": "Microsoft.Extensions.Localization.StringLocalizer<T0>",
|
||||
"Visibility": "Public",
|
||||
"Kind": "Class",
|
||||
"ImplementedInterfaces": [
|
||||
"Microsoft.Extensions.Localization.IStringLocalizer<T0>"
|
||||
],
|
||||
"Members": [
|
||||
{
|
||||
"Kind": "Method",
|
||||
"Name": "get_Item",
|
||||
"Parameters": [
|
||||
{
|
||||
"Name": "name",
|
||||
"Type": "System.String"
|
||||
}
|
||||
],
|
||||
"ReturnType": "Microsoft.Extensions.Localization.LocalizedString",
|
||||
"Virtual": true,
|
||||
"ImplementedInterface": "Microsoft.Extensions.Localization.IStringLocalizer",
|
||||
"Visibility": "Public",
|
||||
"GenericParameter": []
|
||||
},
|
||||
{
|
||||
"Kind": "Method",
|
||||
"Name": "get_Item",
|
||||
"Parameters": [
|
||||
{
|
||||
"Name": "name",
|
||||
"Type": "System.String"
|
||||
},
|
||||
{
|
||||
"Name": "arguments",
|
||||
"Type": "System.Object[]",
|
||||
"IsParams": true
|
||||
}
|
||||
],
|
||||
"ReturnType": "Microsoft.Extensions.Localization.LocalizedString",
|
||||
"Virtual": true,
|
||||
"ImplementedInterface": "Microsoft.Extensions.Localization.IStringLocalizer",
|
||||
"Visibility": "Public",
|
||||
"GenericParameter": []
|
||||
},
|
||||
{
|
||||
"Kind": "Method",
|
||||
"Name": "GetAllStrings",
|
||||
"Parameters": [
|
||||
{
|
||||
"Name": "includeParentCultures",
|
||||
"Type": "System.Boolean"
|
||||
}
|
||||
],
|
||||
"ReturnType": "System.Collections.Generic.IEnumerable<Microsoft.Extensions.Localization.LocalizedString>",
|
||||
"Sealed": true,
|
||||
"Virtual": true,
|
||||
"ImplementedInterface": "Microsoft.Extensions.Localization.IStringLocalizer",
|
||||
"Visibility": "Public",
|
||||
"GenericParameter": []
|
||||
},
|
||||
{
|
||||
"Kind": "Method",
|
||||
"Name": "WithCulture",
|
||||
"Parameters": [
|
||||
{
|
||||
"Name": "culture",
|
||||
"Type": "System.Globalization.CultureInfo"
|
||||
}
|
||||
],
|
||||
"ReturnType": "Microsoft.Extensions.Localization.IStringLocalizer",
|
||||
"Virtual": true,
|
||||
"ImplementedInterface": "Microsoft.Extensions.Localization.IStringLocalizer",
|
||||
"Visibility": "Public",
|
||||
"GenericParameter": []
|
||||
},
|
||||
{
|
||||
"Kind": "Constructor",
|
||||
"Name": ".ctor",
|
||||
"Parameters": [
|
||||
{
|
||||
"Name": "factory",
|
||||
"Type": "Microsoft.Extensions.Localization.IStringLocalizerFactory"
|
||||
}
|
||||
],
|
||||
"Visibility": "Public",
|
||||
"GenericParameter": []
|
||||
}
|
||||
],
|
||||
"GenericParameters": [
|
||||
{
|
||||
"ParameterName": "TResourceSource",
|
||||
"ParameterPosition": 0,
|
||||
"BaseTypeOrInterfaces": []
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
@ -0,0 +1,13 @@
|
|||
<!-- This file is automatically generated. -->
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<PropertyGroup>
|
||||
<TargetFrameworks>netstandard2.0</TargetFrameworks>
|
||||
</PropertyGroup>
|
||||
<ItemGroup Condition="'$(TargetFramework)' == 'netstandard2.0'">
|
||||
<Compile Include="Microsoft.Extensions.Localization.netstandard2.0.cs" />
|
||||
<Reference Include="Microsoft.Extensions.DependencyInjection.Abstractions" />
|
||||
<Reference Include="Microsoft.Extensions.Options" />
|
||||
<Reference Include="Microsoft.Extensions.Localization.Abstractions" />
|
||||
<Reference Include="Microsoft.Extensions.Logging.Abstractions" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
|
|
@ -0,0 +1,93 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
namespace Microsoft.Extensions.DependencyInjection
|
||||
{
|
||||
public static partial class LocalizationServiceCollectionExtensions
|
||||
{
|
||||
public static Microsoft.Extensions.DependencyInjection.IServiceCollection AddLocalization(this Microsoft.Extensions.DependencyInjection.IServiceCollection services) { throw null; }
|
||||
public static Microsoft.Extensions.DependencyInjection.IServiceCollection AddLocalization(this Microsoft.Extensions.DependencyInjection.IServiceCollection services, System.Action<Microsoft.Extensions.Localization.LocalizationOptions> setupAction) { throw null; }
|
||||
}
|
||||
}
|
||||
namespace Microsoft.Extensions.Localization
|
||||
{
|
||||
public partial interface IResourceNamesCache
|
||||
{
|
||||
System.Collections.Generic.IList<string> GetOrAdd(string name, System.Func<string, System.Collections.Generic.IList<string>> valueFactory);
|
||||
}
|
||||
public partial class LocalizationOptions
|
||||
{
|
||||
public LocalizationOptions() { }
|
||||
public string ResourcesPath { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } }
|
||||
}
|
||||
[System.AttributeUsageAttribute(System.AttributeTargets.Assembly, AllowMultiple=false, Inherited=false)]
|
||||
public partial class ResourceLocationAttribute : System.Attribute
|
||||
{
|
||||
public ResourceLocationAttribute(string resourceLocation) { }
|
||||
public string ResourceLocation { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } }
|
||||
}
|
||||
public partial class ResourceManagerStringLocalizer : Microsoft.Extensions.Localization.IStringLocalizer
|
||||
{
|
||||
public ResourceManagerStringLocalizer(System.Resources.ResourceManager resourceManager, Microsoft.Extensions.Localization.Internal.AssemblyWrapper resourceAssemblyWrapper, string baseName, Microsoft.Extensions.Localization.IResourceNamesCache resourceNamesCache, Microsoft.Extensions.Logging.ILogger logger) { }
|
||||
public ResourceManagerStringLocalizer(System.Resources.ResourceManager resourceManager, Microsoft.Extensions.Localization.Internal.IResourceStringProvider resourceStringProvider, string baseName, Microsoft.Extensions.Localization.IResourceNamesCache resourceNamesCache, Microsoft.Extensions.Logging.ILogger logger) { }
|
||||
public ResourceManagerStringLocalizer(System.Resources.ResourceManager resourceManager, System.Reflection.Assembly resourceAssembly, string baseName, Microsoft.Extensions.Localization.IResourceNamesCache resourceNamesCache, Microsoft.Extensions.Logging.ILogger logger) { }
|
||||
public virtual Microsoft.Extensions.Localization.LocalizedString this[string name] { get { throw null; } }
|
||||
public virtual Microsoft.Extensions.Localization.LocalizedString this[string name, params object[] arguments] { get { throw null; } }
|
||||
public virtual System.Collections.Generic.IEnumerable<Microsoft.Extensions.Localization.LocalizedString> GetAllStrings(bool includeParentCultures) { throw null; }
|
||||
protected System.Collections.Generic.IEnumerable<Microsoft.Extensions.Localization.LocalizedString> GetAllStrings(bool includeParentCultures, System.Globalization.CultureInfo culture) { throw null; }
|
||||
protected string GetStringSafely(string name, System.Globalization.CultureInfo culture) { throw null; }
|
||||
[System.ObsoleteAttribute("This method is obsolete. Use `CurrentCulture` and `CurrentUICulture` instead.")]
|
||||
public Microsoft.Extensions.Localization.IStringLocalizer WithCulture(System.Globalization.CultureInfo culture) { throw null; }
|
||||
}
|
||||
public partial class ResourceManagerStringLocalizerFactory : Microsoft.Extensions.Localization.IStringLocalizerFactory
|
||||
{
|
||||
public ResourceManagerStringLocalizerFactory(Microsoft.Extensions.Options.IOptions<Microsoft.Extensions.Localization.LocalizationOptions> localizationOptions, Microsoft.Extensions.Logging.ILoggerFactory loggerFactory) { }
|
||||
public Microsoft.Extensions.Localization.IStringLocalizer Create(string baseName, string location) { throw null; }
|
||||
public Microsoft.Extensions.Localization.IStringLocalizer Create(System.Type resourceSource) { throw null; }
|
||||
protected virtual Microsoft.Extensions.Localization.ResourceManagerStringLocalizer CreateResourceManagerStringLocalizer(System.Reflection.Assembly assembly, string baseName) { throw null; }
|
||||
protected virtual Microsoft.Extensions.Localization.ResourceLocationAttribute GetResourceLocationAttribute(System.Reflection.Assembly assembly) { throw null; }
|
||||
protected virtual string GetResourcePrefix(System.Reflection.TypeInfo typeInfo) { throw null; }
|
||||
protected virtual string GetResourcePrefix(System.Reflection.TypeInfo typeInfo, string baseNamespace, string resourcesRelativePath) { throw null; }
|
||||
protected virtual string GetResourcePrefix(string baseResourceName, string baseNamespace) { throw null; }
|
||||
protected virtual string GetResourcePrefix(string location, string baseName, string resourceLocation) { throw null; }
|
||||
protected virtual Microsoft.Extensions.Localization.RootNamespaceAttribute GetRootNamespaceAttribute(System.Reflection.Assembly assembly) { throw null; }
|
||||
}
|
||||
[System.ObsoleteAttribute("This method is obsolete. Use `CurrentCulture` and `CurrentUICulture` instead.")]
|
||||
public partial class ResourceManagerWithCultureStringLocalizer : Microsoft.Extensions.Localization.ResourceManagerStringLocalizer
|
||||
{
|
||||
public ResourceManagerWithCultureStringLocalizer(System.Resources.ResourceManager resourceManager, System.Reflection.Assembly resourceAssembly, string baseName, Microsoft.Extensions.Localization.IResourceNamesCache resourceNamesCache, System.Globalization.CultureInfo culture, Microsoft.Extensions.Logging.ILogger logger) : base (default(System.Resources.ResourceManager), default(System.Reflection.Assembly), default(string), default(Microsoft.Extensions.Localization.IResourceNamesCache), default(Microsoft.Extensions.Logging.ILogger)) { }
|
||||
public override Microsoft.Extensions.Localization.LocalizedString this[string name] { get { throw null; } }
|
||||
public override Microsoft.Extensions.Localization.LocalizedString this[string name, params object[] arguments] { get { throw null; } }
|
||||
public override System.Collections.Generic.IEnumerable<Microsoft.Extensions.Localization.LocalizedString> GetAllStrings(bool includeParentCultures) { throw null; }
|
||||
}
|
||||
public partial class ResourceNamesCache : Microsoft.Extensions.Localization.IResourceNamesCache
|
||||
{
|
||||
public ResourceNamesCache() { }
|
||||
public System.Collections.Generic.IList<string> GetOrAdd(string name, System.Func<string, System.Collections.Generic.IList<string>> valueFactory) { throw null; }
|
||||
}
|
||||
[System.AttributeUsageAttribute(System.AttributeTargets.Assembly, AllowMultiple=false, Inherited=false)]
|
||||
public partial class RootNamespaceAttribute : System.Attribute
|
||||
{
|
||||
public RootNamespaceAttribute(string rootNamespace) { }
|
||||
public string RootNamespace { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } }
|
||||
}
|
||||
}
|
||||
namespace Microsoft.Extensions.Localization.Internal
|
||||
{
|
||||
public partial class AssemblyWrapper
|
||||
{
|
||||
public AssemblyWrapper(System.Reflection.Assembly assembly) { }
|
||||
public System.Reflection.Assembly Assembly { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } }
|
||||
public virtual string FullName { get { throw null; } }
|
||||
public virtual System.IO.Stream GetManifestResourceStream(string name) { throw null; }
|
||||
}
|
||||
public partial interface IResourceStringProvider
|
||||
{
|
||||
System.Collections.Generic.IList<string> GetAllResourceStrings(System.Globalization.CultureInfo culture, bool throwOnMissing);
|
||||
}
|
||||
public partial class ResourceManagerStringProvider : Microsoft.Extensions.Localization.Internal.IResourceStringProvider
|
||||
{
|
||||
public ResourceManagerStringProvider(Microsoft.Extensions.Localization.IResourceNamesCache resourceCache, System.Resources.ResourceManager resourceManager, System.Reflection.Assembly assembly, string baseName) { }
|
||||
public System.Collections.Generic.IList<string> GetAllResourceStrings(System.Globalization.CultureInfo culture, bool throwOnMissing) { throw null; }
|
||||
}
|
||||
}
|
||||
|
|
@ -15,7 +15,7 @@ namespace Microsoft.Extensions.Localization.Internal
|
|||
{
|
||||
_searchedLocation = LoggerMessage.Define<string, string, CultureInfo>(
|
||||
LogLevel.Debug,
|
||||
1,
|
||||
new EventId(1, "SearchedLocation"),
|
||||
$"{nameof(ResourceManagerStringLocalizer)} searched for '{{Key}}' in '{{LocationSearched}}' with culture '{{Culture}}'.");
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@
|
|||
<NoWarn>$(NoWarn);CS1591</NoWarn>
|
||||
<GenerateDocumentationFile>true</GenerateDocumentationFile>
|
||||
<PackageTags>localization</PackageTags>
|
||||
<IsShipping>true</IsShipping>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
|
@ -16,4 +17,8 @@
|
|||
<Reference Include="Microsoft.Extensions.Logging.Abstractions" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<InternalsVisibleTo Include="Microsoft.Extensions.Localization.Tests" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
|
|
|||
|
|
@ -1,6 +0,0 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
[assembly: InternalsVisibleTo("Microsoft.Extensions.Localization.Tests, PublicKey=0024000004800000940000000602000000240000525341310004000001000100f33a29044fa9d740c9b3213a93e57c84b472c84e0b8a0e1ae48e67a9f8f6de9d5f7f3d52ac23e48ac51801f1dc950abe901da34d2a9e3baadb141a17c77ef3c565dd5ee5054b91cf63bb3c6ab83f72ab3aafe93d0fc3c2348b764fafb0b1c0733de51459aeab46580384bf9d74c4e28164b7cde247f891ba07891c9d872ad2bb")]
|
||||
|
|
@ -1,5 +1,5 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
|
|
@ -151,6 +151,7 @@ namespace Microsoft.Extensions.Localization
|
|||
/// </summary>
|
||||
/// <param name="culture">The <see cref="CultureInfo"/> to use.</param>
|
||||
/// <returns>A culture-specific <see cref="ResourceManagerStringLocalizer"/>.</returns>
|
||||
[Obsolete("This method is obsolete. Use `CurrentCulture` and `CurrentUICulture` instead.")]
|
||||
public IStringLocalizer WithCulture(CultureInfo culture)
|
||||
{
|
||||
return culture == null
|
||||
|
|
@ -271,4 +272,4 @@ namespace Microsoft.Extensions.Localization
|
|||
return resourceNames;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
|
@ -15,6 +15,7 @@ namespace Microsoft.Extensions.Localization
|
|||
/// An <see cref="IStringLocalizer"/> that uses the <see cref="ResourceManager"/> and
|
||||
/// <see cref="ResourceReader"/> to provide localized strings for a specific <see cref="CultureInfo"/>.
|
||||
/// </summary>
|
||||
[Obsolete("This method is obsolete. Use `CurrentCulture` and `CurrentUICulture` instead.")]
|
||||
public class ResourceManagerWithCultureStringLocalizer : ResourceManagerStringLocalizer
|
||||
{
|
||||
private readonly string _resourceBaseName;
|
||||
|
|
@ -161,4 +162,4 @@ namespace Microsoft.Extensions.Localization
|
|||
public override IEnumerable<LocalizedString> GetAllStrings(bool includeParentCultures) =>
|
||||
GetAllStrings(includeParentCultures, _culture);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,687 +0,0 @@
|
|||
{
|
||||
"AssemblyIdentity": "Microsoft.Extensions.Localization, Version=2.1.1.0, Culture=neutral, PublicKeyToken=adb9793829ddae60",
|
||||
"Types": [
|
||||
{
|
||||
"Name": "Microsoft.Extensions.DependencyInjection.LocalizationServiceCollectionExtensions",
|
||||
"Visibility": "Public",
|
||||
"Kind": "Class",
|
||||
"Abstract": true,
|
||||
"Static": true,
|
||||
"Sealed": true,
|
||||
"ImplementedInterfaces": [],
|
||||
"Members": [
|
||||
{
|
||||
"Kind": "Method",
|
||||
"Name": "AddLocalization",
|
||||
"Parameters": [
|
||||
{
|
||||
"Name": "services",
|
||||
"Type": "Microsoft.Extensions.DependencyInjection.IServiceCollection"
|
||||
}
|
||||
],
|
||||
"ReturnType": "Microsoft.Extensions.DependencyInjection.IServiceCollection",
|
||||
"Static": true,
|
||||
"Extension": true,
|
||||
"Visibility": "Public",
|
||||
"GenericParameter": []
|
||||
},
|
||||
{
|
||||
"Kind": "Method",
|
||||
"Name": "AddLocalization",
|
||||
"Parameters": [
|
||||
{
|
||||
"Name": "services",
|
||||
"Type": "Microsoft.Extensions.DependencyInjection.IServiceCollection"
|
||||
},
|
||||
{
|
||||
"Name": "setupAction",
|
||||
"Type": "System.Action<Microsoft.Extensions.Localization.LocalizationOptions>"
|
||||
}
|
||||
],
|
||||
"ReturnType": "Microsoft.Extensions.DependencyInjection.IServiceCollection",
|
||||
"Static": true,
|
||||
"Extension": true,
|
||||
"Visibility": "Public",
|
||||
"GenericParameter": []
|
||||
}
|
||||
],
|
||||
"GenericParameters": []
|
||||
},
|
||||
{
|
||||
"Name": "Microsoft.Extensions.Localization.IResourceNamesCache",
|
||||
"Visibility": "Public",
|
||||
"Kind": "Interface",
|
||||
"Abstract": true,
|
||||
"ImplementedInterfaces": [],
|
||||
"Members": [
|
||||
{
|
||||
"Kind": "Method",
|
||||
"Name": "GetOrAdd",
|
||||
"Parameters": [
|
||||
{
|
||||
"Name": "name",
|
||||
"Type": "System.String"
|
||||
},
|
||||
{
|
||||
"Name": "valueFactory",
|
||||
"Type": "System.Func<System.String, System.Collections.Generic.IList<System.String>>"
|
||||
}
|
||||
],
|
||||
"ReturnType": "System.Collections.Generic.IList<System.String>",
|
||||
"GenericParameter": []
|
||||
}
|
||||
],
|
||||
"GenericParameters": []
|
||||
},
|
||||
{
|
||||
"Name": "Microsoft.Extensions.Localization.LocalizationOptions",
|
||||
"Visibility": "Public",
|
||||
"Kind": "Class",
|
||||
"ImplementedInterfaces": [],
|
||||
"Members": [
|
||||
{
|
||||
"Kind": "Method",
|
||||
"Name": "get_ResourcesPath",
|
||||
"Parameters": [],
|
||||
"ReturnType": "System.String",
|
||||
"Visibility": "Public",
|
||||
"GenericParameter": []
|
||||
},
|
||||
{
|
||||
"Kind": "Method",
|
||||
"Name": "set_ResourcesPath",
|
||||
"Parameters": [
|
||||
{
|
||||
"Name": "value",
|
||||
"Type": "System.String"
|
||||
}
|
||||
],
|
||||
"ReturnType": "System.Void",
|
||||
"Visibility": "Public",
|
||||
"GenericParameter": []
|
||||
},
|
||||
{
|
||||
"Kind": "Constructor",
|
||||
"Name": ".ctor",
|
||||
"Parameters": [],
|
||||
"Visibility": "Public",
|
||||
"GenericParameter": []
|
||||
}
|
||||
],
|
||||
"GenericParameters": []
|
||||
},
|
||||
{
|
||||
"Name": "Microsoft.Extensions.Localization.ResourceLocationAttribute",
|
||||
"Visibility": "Public",
|
||||
"Kind": "Class",
|
||||
"BaseType": "System.Attribute",
|
||||
"ImplementedInterfaces": [],
|
||||
"Members": [
|
||||
{
|
||||
"Kind": "Method",
|
||||
"Name": "get_ResourceLocation",
|
||||
"Parameters": [],
|
||||
"ReturnType": "System.String",
|
||||
"Visibility": "Public",
|
||||
"GenericParameter": []
|
||||
},
|
||||
{
|
||||
"Kind": "Constructor",
|
||||
"Name": ".ctor",
|
||||
"Parameters": [
|
||||
{
|
||||
"Name": "resourceLocation",
|
||||
"Type": "System.String"
|
||||
}
|
||||
],
|
||||
"Visibility": "Public",
|
||||
"GenericParameter": []
|
||||
}
|
||||
],
|
||||
"GenericParameters": []
|
||||
},
|
||||
{
|
||||
"Name": "Microsoft.Extensions.Localization.ResourceManagerStringLocalizer",
|
||||
"Visibility": "Public",
|
||||
"Kind": "Class",
|
||||
"ImplementedInterfaces": [
|
||||
"Microsoft.Extensions.Localization.IStringLocalizer"
|
||||
],
|
||||
"Members": [
|
||||
{
|
||||
"Kind": "Method",
|
||||
"Name": "get_Item",
|
||||
"Parameters": [
|
||||
{
|
||||
"Name": "name",
|
||||
"Type": "System.String"
|
||||
}
|
||||
],
|
||||
"ReturnType": "Microsoft.Extensions.Localization.LocalizedString",
|
||||
"Virtual": true,
|
||||
"ImplementedInterface": "Microsoft.Extensions.Localization.IStringLocalizer",
|
||||
"Visibility": "Public",
|
||||
"GenericParameter": []
|
||||
},
|
||||
{
|
||||
"Kind": "Method",
|
||||
"Name": "get_Item",
|
||||
"Parameters": [
|
||||
{
|
||||
"Name": "name",
|
||||
"Type": "System.String"
|
||||
},
|
||||
{
|
||||
"Name": "arguments",
|
||||
"Type": "System.Object[]",
|
||||
"IsParams": true
|
||||
}
|
||||
],
|
||||
"ReturnType": "Microsoft.Extensions.Localization.LocalizedString",
|
||||
"Virtual": true,
|
||||
"ImplementedInterface": "Microsoft.Extensions.Localization.IStringLocalizer",
|
||||
"Visibility": "Public",
|
||||
"GenericParameter": []
|
||||
},
|
||||
{
|
||||
"Kind": "Method",
|
||||
"Name": "WithCulture",
|
||||
"Parameters": [
|
||||
{
|
||||
"Name": "culture",
|
||||
"Type": "System.Globalization.CultureInfo"
|
||||
}
|
||||
],
|
||||
"ReturnType": "Microsoft.Extensions.Localization.IStringLocalizer",
|
||||
"Sealed": true,
|
||||
"Virtual": true,
|
||||
"ImplementedInterface": "Microsoft.Extensions.Localization.IStringLocalizer",
|
||||
"Visibility": "Public",
|
||||
"GenericParameter": []
|
||||
},
|
||||
{
|
||||
"Kind": "Method",
|
||||
"Name": "GetAllStrings",
|
||||
"Parameters": [
|
||||
{
|
||||
"Name": "includeParentCultures",
|
||||
"Type": "System.Boolean"
|
||||
}
|
||||
],
|
||||
"ReturnType": "System.Collections.Generic.IEnumerable<Microsoft.Extensions.Localization.LocalizedString>",
|
||||
"Virtual": true,
|
||||
"ImplementedInterface": "Microsoft.Extensions.Localization.IStringLocalizer",
|
||||
"Visibility": "Public",
|
||||
"GenericParameter": []
|
||||
},
|
||||
{
|
||||
"Kind": "Method",
|
||||
"Name": "GetAllStrings",
|
||||
"Parameters": [
|
||||
{
|
||||
"Name": "includeParentCultures",
|
||||
"Type": "System.Boolean"
|
||||
},
|
||||
{
|
||||
"Name": "culture",
|
||||
"Type": "System.Globalization.CultureInfo"
|
||||
}
|
||||
],
|
||||
"ReturnType": "System.Collections.Generic.IEnumerable<Microsoft.Extensions.Localization.LocalizedString>",
|
||||
"Visibility": "Protected",
|
||||
"GenericParameter": []
|
||||
},
|
||||
{
|
||||
"Kind": "Method",
|
||||
"Name": "GetStringSafely",
|
||||
"Parameters": [
|
||||
{
|
||||
"Name": "name",
|
||||
"Type": "System.String"
|
||||
},
|
||||
{
|
||||
"Name": "culture",
|
||||
"Type": "System.Globalization.CultureInfo"
|
||||
}
|
||||
],
|
||||
"ReturnType": "System.String",
|
||||
"Visibility": "Protected",
|
||||
"GenericParameter": []
|
||||
},
|
||||
{
|
||||
"Kind": "Constructor",
|
||||
"Name": ".ctor",
|
||||
"Parameters": [
|
||||
{
|
||||
"Name": "resourceManager",
|
||||
"Type": "System.Resources.ResourceManager"
|
||||
},
|
||||
{
|
||||
"Name": "resourceAssembly",
|
||||
"Type": "System.Reflection.Assembly"
|
||||
},
|
||||
{
|
||||
"Name": "baseName",
|
||||
"Type": "System.String"
|
||||
},
|
||||
{
|
||||
"Name": "resourceNamesCache",
|
||||
"Type": "Microsoft.Extensions.Localization.IResourceNamesCache"
|
||||
},
|
||||
{
|
||||
"Name": "logger",
|
||||
"Type": "Microsoft.Extensions.Logging.ILogger"
|
||||
}
|
||||
],
|
||||
"Visibility": "Public",
|
||||
"GenericParameter": []
|
||||
},
|
||||
{
|
||||
"Kind": "Constructor",
|
||||
"Name": ".ctor",
|
||||
"Parameters": [
|
||||
{
|
||||
"Name": "resourceManager",
|
||||
"Type": "System.Resources.ResourceManager"
|
||||
},
|
||||
{
|
||||
"Name": "resourceAssemblyWrapper",
|
||||
"Type": "Microsoft.Extensions.Localization.Internal.AssemblyWrapper"
|
||||
},
|
||||
{
|
||||
"Name": "baseName",
|
||||
"Type": "System.String"
|
||||
},
|
||||
{
|
||||
"Name": "resourceNamesCache",
|
||||
"Type": "Microsoft.Extensions.Localization.IResourceNamesCache"
|
||||
},
|
||||
{
|
||||
"Name": "logger",
|
||||
"Type": "Microsoft.Extensions.Logging.ILogger"
|
||||
}
|
||||
],
|
||||
"Visibility": "Public",
|
||||
"GenericParameter": []
|
||||
},
|
||||
{
|
||||
"Kind": "Constructor",
|
||||
"Name": ".ctor",
|
||||
"Parameters": [
|
||||
{
|
||||
"Name": "resourceManager",
|
||||
"Type": "System.Resources.ResourceManager"
|
||||
},
|
||||
{
|
||||
"Name": "resourceStringProvider",
|
||||
"Type": "Microsoft.Extensions.Localization.Internal.IResourceStringProvider"
|
||||
},
|
||||
{
|
||||
"Name": "baseName",
|
||||
"Type": "System.String"
|
||||
},
|
||||
{
|
||||
"Name": "resourceNamesCache",
|
||||
"Type": "Microsoft.Extensions.Localization.IResourceNamesCache"
|
||||
},
|
||||
{
|
||||
"Name": "logger",
|
||||
"Type": "Microsoft.Extensions.Logging.ILogger"
|
||||
}
|
||||
],
|
||||
"Visibility": "Public",
|
||||
"GenericParameter": []
|
||||
}
|
||||
],
|
||||
"GenericParameters": []
|
||||
},
|
||||
{
|
||||
"Name": "Microsoft.Extensions.Localization.ResourceManagerStringLocalizerFactory",
|
||||
"Visibility": "Public",
|
||||
"Kind": "Class",
|
||||
"ImplementedInterfaces": [
|
||||
"Microsoft.Extensions.Localization.IStringLocalizerFactory"
|
||||
],
|
||||
"Members": [
|
||||
{
|
||||
"Kind": "Method",
|
||||
"Name": "GetResourcePrefix",
|
||||
"Parameters": [
|
||||
{
|
||||
"Name": "typeInfo",
|
||||
"Type": "System.Reflection.TypeInfo"
|
||||
}
|
||||
],
|
||||
"ReturnType": "System.String",
|
||||
"Virtual": true,
|
||||
"Visibility": "Protected",
|
||||
"GenericParameter": []
|
||||
},
|
||||
{
|
||||
"Kind": "Method",
|
||||
"Name": "GetResourcePrefix",
|
||||
"Parameters": [
|
||||
{
|
||||
"Name": "typeInfo",
|
||||
"Type": "System.Reflection.TypeInfo"
|
||||
},
|
||||
{
|
||||
"Name": "baseNamespace",
|
||||
"Type": "System.String"
|
||||
},
|
||||
{
|
||||
"Name": "resourcesRelativePath",
|
||||
"Type": "System.String"
|
||||
}
|
||||
],
|
||||
"ReturnType": "System.String",
|
||||
"Virtual": true,
|
||||
"Visibility": "Protected",
|
||||
"GenericParameter": []
|
||||
},
|
||||
{
|
||||
"Kind": "Method",
|
||||
"Name": "GetResourcePrefix",
|
||||
"Parameters": [
|
||||
{
|
||||
"Name": "baseResourceName",
|
||||
"Type": "System.String"
|
||||
},
|
||||
{
|
||||
"Name": "baseNamespace",
|
||||
"Type": "System.String"
|
||||
}
|
||||
],
|
||||
"ReturnType": "System.String",
|
||||
"Virtual": true,
|
||||
"Visibility": "Protected",
|
||||
"GenericParameter": []
|
||||
},
|
||||
{
|
||||
"Kind": "Method",
|
||||
"Name": "Create",
|
||||
"Parameters": [
|
||||
{
|
||||
"Name": "resourceSource",
|
||||
"Type": "System.Type"
|
||||
}
|
||||
],
|
||||
"ReturnType": "Microsoft.Extensions.Localization.IStringLocalizer",
|
||||
"Sealed": true,
|
||||
"Virtual": true,
|
||||
"ImplementedInterface": "Microsoft.Extensions.Localization.IStringLocalizerFactory",
|
||||
"Visibility": "Public",
|
||||
"GenericParameter": []
|
||||
},
|
||||
{
|
||||
"Kind": "Method",
|
||||
"Name": "Create",
|
||||
"Parameters": [
|
||||
{
|
||||
"Name": "baseName",
|
||||
"Type": "System.String"
|
||||
},
|
||||
{
|
||||
"Name": "location",
|
||||
"Type": "System.String"
|
||||
}
|
||||
],
|
||||
"ReturnType": "Microsoft.Extensions.Localization.IStringLocalizer",
|
||||
"Sealed": true,
|
||||
"Virtual": true,
|
||||
"ImplementedInterface": "Microsoft.Extensions.Localization.IStringLocalizerFactory",
|
||||
"Visibility": "Public",
|
||||
"GenericParameter": []
|
||||
},
|
||||
{
|
||||
"Kind": "Method",
|
||||
"Name": "CreateResourceManagerStringLocalizer",
|
||||
"Parameters": [
|
||||
{
|
||||
"Name": "assembly",
|
||||
"Type": "System.Reflection.Assembly"
|
||||
},
|
||||
{
|
||||
"Name": "baseName",
|
||||
"Type": "System.String"
|
||||
}
|
||||
],
|
||||
"ReturnType": "Microsoft.Extensions.Localization.ResourceManagerStringLocalizer",
|
||||
"Virtual": true,
|
||||
"Visibility": "Protected",
|
||||
"GenericParameter": []
|
||||
},
|
||||
{
|
||||
"Kind": "Method",
|
||||
"Name": "GetResourcePrefix",
|
||||
"Parameters": [
|
||||
{
|
||||
"Name": "location",
|
||||
"Type": "System.String"
|
||||
},
|
||||
{
|
||||
"Name": "baseName",
|
||||
"Type": "System.String"
|
||||
},
|
||||
{
|
||||
"Name": "resourceLocation",
|
||||
"Type": "System.String"
|
||||
}
|
||||
],
|
||||
"ReturnType": "System.String",
|
||||
"Virtual": true,
|
||||
"Visibility": "Protected",
|
||||
"GenericParameter": []
|
||||
},
|
||||
{
|
||||
"Kind": "Method",
|
||||
"Name": "GetResourceLocationAttribute",
|
||||
"Parameters": [
|
||||
{
|
||||
"Name": "assembly",
|
||||
"Type": "System.Reflection.Assembly"
|
||||
}
|
||||
],
|
||||
"ReturnType": "Microsoft.Extensions.Localization.ResourceLocationAttribute",
|
||||
"Virtual": true,
|
||||
"Visibility": "Protected",
|
||||
"GenericParameter": []
|
||||
},
|
||||
{
|
||||
"Kind": "Method",
|
||||
"Name": "GetRootNamespaceAttribute",
|
||||
"Parameters": [
|
||||
{
|
||||
"Name": "assembly",
|
||||
"Type": "System.Reflection.Assembly"
|
||||
}
|
||||
],
|
||||
"ReturnType": "Microsoft.Extensions.Localization.RootNamespaceAttribute",
|
||||
"Virtual": true,
|
||||
"Visibility": "Protected",
|
||||
"GenericParameter": []
|
||||
},
|
||||
{
|
||||
"Kind": "Constructor",
|
||||
"Name": ".ctor",
|
||||
"Parameters": [
|
||||
{
|
||||
"Name": "localizationOptions",
|
||||
"Type": "Microsoft.Extensions.Options.IOptions<Microsoft.Extensions.Localization.LocalizationOptions>"
|
||||
},
|
||||
{
|
||||
"Name": "loggerFactory",
|
||||
"Type": "Microsoft.Extensions.Logging.ILoggerFactory"
|
||||
}
|
||||
],
|
||||
"Visibility": "Public",
|
||||
"GenericParameter": []
|
||||
}
|
||||
],
|
||||
"GenericParameters": []
|
||||
},
|
||||
{
|
||||
"Name": "Microsoft.Extensions.Localization.ResourceManagerWithCultureStringLocalizer",
|
||||
"Visibility": "Public",
|
||||
"Kind": "Class",
|
||||
"BaseType": "Microsoft.Extensions.Localization.ResourceManagerStringLocalizer",
|
||||
"ImplementedInterfaces": [],
|
||||
"Members": [
|
||||
{
|
||||
"Kind": "Method",
|
||||
"Name": "get_Item",
|
||||
"Parameters": [
|
||||
{
|
||||
"Name": "name",
|
||||
"Type": "System.String"
|
||||
}
|
||||
],
|
||||
"ReturnType": "Microsoft.Extensions.Localization.LocalizedString",
|
||||
"Virtual": true,
|
||||
"Override": true,
|
||||
"ImplementedInterface": "Microsoft.Extensions.Localization.IStringLocalizer",
|
||||
"Visibility": "Public",
|
||||
"GenericParameter": []
|
||||
},
|
||||
{
|
||||
"Kind": "Method",
|
||||
"Name": "get_Item",
|
||||
"Parameters": [
|
||||
{
|
||||
"Name": "name",
|
||||
"Type": "System.String"
|
||||
},
|
||||
{
|
||||
"Name": "arguments",
|
||||
"Type": "System.Object[]",
|
||||
"IsParams": true
|
||||
}
|
||||
],
|
||||
"ReturnType": "Microsoft.Extensions.Localization.LocalizedString",
|
||||
"Virtual": true,
|
||||
"Override": true,
|
||||
"ImplementedInterface": "Microsoft.Extensions.Localization.IStringLocalizer",
|
||||
"Visibility": "Public",
|
||||
"GenericParameter": []
|
||||
},
|
||||
{
|
||||
"Kind": "Method",
|
||||
"Name": "GetAllStrings",
|
||||
"Parameters": [
|
||||
{
|
||||
"Name": "includeParentCultures",
|
||||
"Type": "System.Boolean"
|
||||
}
|
||||
],
|
||||
"ReturnType": "System.Collections.Generic.IEnumerable<Microsoft.Extensions.Localization.LocalizedString>",
|
||||
"Virtual": true,
|
||||
"Override": true,
|
||||
"ImplementedInterface": "Microsoft.Extensions.Localization.IStringLocalizer",
|
||||
"Visibility": "Public",
|
||||
"GenericParameter": []
|
||||
},
|
||||
{
|
||||
"Kind": "Constructor",
|
||||
"Name": ".ctor",
|
||||
"Parameters": [
|
||||
{
|
||||
"Name": "resourceManager",
|
||||
"Type": "System.Resources.ResourceManager"
|
||||
},
|
||||
{
|
||||
"Name": "resourceAssembly",
|
||||
"Type": "System.Reflection.Assembly"
|
||||
},
|
||||
{
|
||||
"Name": "baseName",
|
||||
"Type": "System.String"
|
||||
},
|
||||
{
|
||||
"Name": "resourceNamesCache",
|
||||
"Type": "Microsoft.Extensions.Localization.IResourceNamesCache"
|
||||
},
|
||||
{
|
||||
"Name": "culture",
|
||||
"Type": "System.Globalization.CultureInfo"
|
||||
},
|
||||
{
|
||||
"Name": "logger",
|
||||
"Type": "Microsoft.Extensions.Logging.ILogger"
|
||||
}
|
||||
],
|
||||
"Visibility": "Public",
|
||||
"GenericParameter": []
|
||||
}
|
||||
],
|
||||
"GenericParameters": []
|
||||
},
|
||||
{
|
||||
"Name": "Microsoft.Extensions.Localization.ResourceNamesCache",
|
||||
"Visibility": "Public",
|
||||
"Kind": "Class",
|
||||
"ImplementedInterfaces": [
|
||||
"Microsoft.Extensions.Localization.IResourceNamesCache"
|
||||
],
|
||||
"Members": [
|
||||
{
|
||||
"Kind": "Method",
|
||||
"Name": "GetOrAdd",
|
||||
"Parameters": [
|
||||
{
|
||||
"Name": "name",
|
||||
"Type": "System.String"
|
||||
},
|
||||
{
|
||||
"Name": "valueFactory",
|
||||
"Type": "System.Func<System.String, System.Collections.Generic.IList<System.String>>"
|
||||
}
|
||||
],
|
||||
"ReturnType": "System.Collections.Generic.IList<System.String>",
|
||||
"Sealed": true,
|
||||
"Virtual": true,
|
||||
"ImplementedInterface": "Microsoft.Extensions.Localization.IResourceNamesCache",
|
||||
"Visibility": "Public",
|
||||
"GenericParameter": []
|
||||
},
|
||||
{
|
||||
"Kind": "Constructor",
|
||||
"Name": ".ctor",
|
||||
"Parameters": [],
|
||||
"Visibility": "Public",
|
||||
"GenericParameter": []
|
||||
}
|
||||
],
|
||||
"GenericParameters": []
|
||||
},
|
||||
{
|
||||
"Name": "Microsoft.Extensions.Localization.RootNamespaceAttribute",
|
||||
"Visibility": "Public",
|
||||
"Kind": "Class",
|
||||
"BaseType": "System.Attribute",
|
||||
"ImplementedInterfaces": [],
|
||||
"Members": [
|
||||
{
|
||||
"Kind": "Method",
|
||||
"Name": "get_RootNamespace",
|
||||
"Parameters": [],
|
||||
"ReturnType": "System.String",
|
||||
"Visibility": "Public",
|
||||
"GenericParameter": []
|
||||
},
|
||||
{
|
||||
"Kind": "Constructor",
|
||||
"Name": ".ctor",
|
||||
"Parameters": [
|
||||
{
|
||||
"Name": "rootNamespace",
|
||||
"Type": "System.String"
|
||||
}
|
||||
],
|
||||
"Visibility": "Public",
|
||||
"GenericParameter": []
|
||||
}
|
||||
],
|
||||
"GenericParameters": []
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
@ -1,7 +1,7 @@
|
|||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFrameworks>netcoreapp2.2;net461</TargetFrameworks>
|
||||
<TargetFrameworks>netcoreapp3.0;net472</TargetFrameworks>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
|
|
|||
|
|
@ -182,12 +182,11 @@ namespace Microsoft.Extensions.Localization
|
|||
var resourceManager = new TestResourceManager(baseName, resourceAssembly);
|
||||
var logger = Logger;
|
||||
|
||||
var localizer = new ResourceManagerWithCultureStringLocalizer(
|
||||
var localizer = new ResourceManagerStringLocalizer(
|
||||
resourceManager,
|
||||
resourceAssembly.Assembly,
|
||||
baseName,
|
||||
resourceNamesCache,
|
||||
CultureInfo.CurrentCulture,
|
||||
logger);
|
||||
|
||||
// Act & Assert
|
||||
|
|
@ -291,7 +290,7 @@ namespace Microsoft.Extensions.Localization
|
|||
public override Stream GetManifestResourceStream(string name)
|
||||
{
|
||||
ManifestResourceStreamCallCount++;
|
||||
|
||||
|
||||
return HasResources ? MakeResourceStream() : null;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,159 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using System.Globalization;
|
||||
using Moq;
|
||||
using Xunit;
|
||||
|
||||
namespace Microsoft.Extensions.Localization
|
||||
{
|
||||
public class StringLocalizerOfTTest
|
||||
{
|
||||
[Fact]
|
||||
public void Constructor_ThrowsAnExceptionForNullFactory()
|
||||
{
|
||||
// Arrange, act and assert
|
||||
var exception = Assert.Throws<ArgumentNullException>(
|
||||
() => new StringLocalizer<object>(factory: null));
|
||||
|
||||
Assert.Equal("factory", exception.ParamName);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Constructor_ResolvesLocalizerFromFactory()
|
||||
{
|
||||
// Arrange
|
||||
var factory = new Mock<IStringLocalizerFactory>();
|
||||
|
||||
// Act
|
||||
_ = new StringLocalizer<object>(factory.Object);
|
||||
|
||||
// Assert
|
||||
factory.Verify(mock => mock.Create(typeof(object)), Times.Once());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void WithCulture_InvokesWithCultureFromInnerLocalizer()
|
||||
{
|
||||
// Arrange
|
||||
var factory = new Mock<IStringLocalizerFactory>();
|
||||
var innerLocalizer = new Mock<IStringLocalizer>();
|
||||
factory.Setup(mock => mock.Create(typeof(object)))
|
||||
.Returns(innerLocalizer.Object);
|
||||
|
||||
var localizer = new StringLocalizer<object>(factory.Object);
|
||||
|
||||
// Act
|
||||
#pragma warning disable CS0618
|
||||
localizer.WithCulture(CultureInfo.GetCultureInfo("fr-FR"));
|
||||
#pragma warning restore CS0618
|
||||
|
||||
// Assert
|
||||
#pragma warning disable CS0618
|
||||
innerLocalizer.Verify(mock => mock.WithCulture(CultureInfo.GetCultureInfo("fr-FR")), Times.Once());
|
||||
#pragma warning restore CS0618
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Indexer_ThrowsAnExceptionForNullName()
|
||||
{
|
||||
// Arrange
|
||||
var factory = new Mock<IStringLocalizerFactory>();
|
||||
var innerLocalizer = new Mock<IStringLocalizer>();
|
||||
factory.Setup(mock => mock.Create(typeof(object)))
|
||||
.Returns(innerLocalizer.Object);
|
||||
|
||||
var localizer = new StringLocalizer<object>(factory.Object);
|
||||
|
||||
// Act and assert
|
||||
var exception = Assert.Throws<ArgumentNullException>(() => localizer[name: null]);
|
||||
|
||||
Assert.Equal("name", exception.ParamName);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Indexer_InvokesIndexerFromInnerLocalizer()
|
||||
{
|
||||
// Arrange
|
||||
var factory = new Mock<IStringLocalizerFactory>();
|
||||
var innerLocalizer = new Mock<IStringLocalizer>();
|
||||
factory.Setup(mock => mock.Create(typeof(object)))
|
||||
.Returns(innerLocalizer.Object);
|
||||
|
||||
var localizer = new StringLocalizer<object>(factory.Object);
|
||||
|
||||
// Act
|
||||
_ = localizer["Hello world"];
|
||||
|
||||
// Assert
|
||||
innerLocalizer.Verify(mock => mock["Hello world"], Times.Once());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Indexer_ThrowsAnExceptionForNullName_WithArguments()
|
||||
{
|
||||
// Arrange
|
||||
var factory = new Mock<IStringLocalizerFactory>();
|
||||
var innerLocalizer = new Mock<IStringLocalizer>();
|
||||
factory.Setup(mock => mock.Create(typeof(object)))
|
||||
.Returns(innerLocalizer.Object);
|
||||
|
||||
var localizer = new StringLocalizer<object>(factory.Object);
|
||||
|
||||
// Act and assert
|
||||
var exception = Assert.Throws<ArgumentNullException>(() => localizer[name: null]);
|
||||
|
||||
Assert.Equal("name", exception.ParamName);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Indexer_InvokesIndexerFromInnerLocalizer_WithArguments()
|
||||
{
|
||||
// Arrange
|
||||
var factory = new Mock<IStringLocalizerFactory>();
|
||||
var innerLocalizer = new Mock<IStringLocalizer>();
|
||||
factory.Setup(mock => mock.Create(typeof(object)))
|
||||
.Returns(innerLocalizer.Object);
|
||||
|
||||
var localizer = new StringLocalizer<object>(factory.Object);
|
||||
|
||||
// Act
|
||||
_ = localizer["Welcome, {0}", "Bob"];
|
||||
|
||||
// Assert
|
||||
innerLocalizer.Verify(mock => mock["Welcome, {0}", "Bob"], Times.Once());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GetAllStrings_InvokesGetAllStringsFromInnerLocalizer()
|
||||
{
|
||||
// Arrange
|
||||
var factory = new Mock<IStringLocalizerFactory>();
|
||||
var innerLocalizer = new Mock<IStringLocalizer>();
|
||||
factory.Setup(mock => mock.Create(typeof(object)))
|
||||
.Returns(innerLocalizer.Object);
|
||||
|
||||
var localizer = new StringLocalizer<object>(factory.Object);
|
||||
|
||||
// Act
|
||||
localizer.GetAllStrings(includeParentCultures: true);
|
||||
|
||||
// Assert
|
||||
innerLocalizer.Verify(mock => mock.GetAllStrings(true), Times.Once());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void StringLocalizer_CanBeCastToBaseType()
|
||||
{
|
||||
// Arrange and act
|
||||
IStringLocalizer<BaseType> localizer = new StringLocalizer<DerivedType>(Mock.Of<IStringLocalizerFactory>());
|
||||
|
||||
// Assert
|
||||
Assert.NotNull(localizer);
|
||||
}
|
||||
|
||||
private class BaseType { }
|
||||
private class DerivedType : BaseType { }
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue