Merge branch 'master' into merge/release/2.2-to-master\n\nCommit migrated from ba9e5a821b

This commit is contained in:
Doug Bunting 2019-04-12 09:24:28 -07:00 committed by GitHub
commit fa96813652
175 changed files with 9118 additions and 3049 deletions

View File

@ -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>

View File

@ -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>

View File

@ -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; }
}
}

View File

@ -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")})";
}
}

View File

@ -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>

View File

@ -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);
}
}
}
}

View File

@ -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")}
})
}
})
}
};
}
}
}

View File

@ -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));
}
}
}

View File

@ -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>

View File

@ -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>

View File

@ -1,7 +0,0 @@
<Project>
<Import Project="$([MSBuild]::GetDirectoryNameOfFileAbove($(MSBuildThisFileDirectory)..\, Directory.Build.props))\Directory.Build.props" />
<PropertyGroup>
<IsProductComponent>true</IsProductComponent>
</PropertyGroup>
</Project>

View File

@ -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>

View File

@ -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; }
}
}

View File

@ -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>

View File

@ -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>

View File

@ -1,4 +0,0 @@

using System.Runtime.CompilerServices;
[assembly: InternalsVisibleTo("Microsoft.Extensions.FileProviders.Embedded.Tests, PublicKey=0024000004800000940000000602000000240000525341310004000001000100f33a29044fa9d740c9b3213a93e57c84b472c84e0b8a0e1ae48e67a9f8f6de9d5f7f3d52ac23e48ac51801f1dc950abe901da34d2a9e3baadb141a17c77ef3c565dd5ee5054b91cf63bb3c6ab83f72ab3aafe93d0fc3c2348b764fafb0b1c0733de51459aeab46580384bf9d74c4e28164b7cde247f891ba07891c9d872ad2bb")]

View File

@ -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": []
}
]
}

View File

@ -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

View File

@ -1,7 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFrameworks>netcoreapp2.2;net461</TargetFrameworks>
<TargetFrameworks>netcoreapp3.0;net472</TargetFrameworks>
</PropertyGroup>
<ItemGroup>

View File

@ -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>

View File

@ -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>

View File

@ -1,7 +0,0 @@
<Project>
<Import Project="$([MSBuild]::GetDirectoryNameOfFileAbove($(MSBuildThisFileDirectory)..\, Directory.Build.props))\Directory.Build.props" />
<PropertyGroup>
<IsProductComponent>true</IsProductComponent>
</PropertyGroup>
</Project>

View File

@ -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>

View File

@ -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);
}
}

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -1,5 +0,0 @@
{
"AssemblyIdentity": "Microsoft.Extensions.Diagnostics.HealthChecks.Abstractions, Version=2.1.1.0, Culture=neutral, PublicKeyToken=adb9793829ddae60",
"Types": [
]
}

View File

@ -1,7 +0,0 @@
<Project>
<Import Project="$([MSBuild]::GetDirectoryNameOfFileAbove($(MSBuildThisFileDirectory)..\, Directory.Build.props))\Directory.Build.props" />
<PropertyGroup>
<IsProductComponent>true</IsProductComponent>
</PropertyGroup>
</Project>

View File

@ -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>

View File

@ -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; } }
}
}

View File

@ -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;
}
}

View File

@ -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);
}
}

View File

@ -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));
}
}
}

View File

@ -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));
}
}
}

View File

@ -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" />

View File

@ -1,3 +0,0 @@
using System.Runtime.CompilerServices;
[assembly: InternalsVisibleTo("Microsoft.Extensions.Diagnostics.HealthChecks.Tests, PublicKey=0024000004800000940000000602000000240000525341310004000001000100f33a29044fa9d740c9b3213a93e57c84b472c84e0b8a0e1ae48e67a9f8f6de9d5f7f3d52ac23e48ac51801f1dc950abe901da34d2a9e3baadb141a17c77ef3c565dd5ee5054b91cf63bb3c6ab83f72ab3aafe93d0fc3c2348b764fafb0b1c0733de51459aeab46580384bf9d74c4e28164b7cde247f891ba07891c9d872ad2bb")]

View File

@ -1,5 +0,0 @@
{
"AssemblyIdentity": "Microsoft.Extensions.Diagnostics.HealthChecks, Version=2.1.1.0, Culture=neutral, PublicKeyToken=adb9793829ddae60",
"Types": [
]
}

View File

@ -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();

View File

@ -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();
}
}
}
}

View File

@ -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); },

View File

@ -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>

View File

@ -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>

View File

@ -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
}
}
}

View File

@ -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"
}
}

View File

@ -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;
}
}

View File

@ -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/**"
]
}

View File

@ -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
}
}

View File

@ -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>

View File

@ -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() { }
}
}

View File

@ -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}'.");
}
}
}

View File

@ -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);
}
}
}

View File

@ -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();
}
}

View File

@ -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);
}
}

View File

@ -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);
}
}

View File

@ -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);
}
}
}
}
}

View File

@ -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;
}
}
}

View File

@ -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)
{
}
}
}

View File

@ -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);
}
}

View File

@ -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;
}
}
}

View File

@ -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));
}
}
}

View File

@ -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()));
}
}
}
}

View File

@ -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);
}
}
}

View File

@ -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);
}
}

View File

@ -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

View File

@ -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>

View File

@ -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));
});
}
}
}

View File

@ -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;
}
}
}
}

View File

@ -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);
}
}
}

View File

@ -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");
}
}
}

View File

@ -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 },
};
}
}
}
}

View File

@ -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();
}
}
}

View File

@ -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; }
}
}
}

View File

@ -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>

View File

@ -0,0 +1,3 @@
{
"shadowCopy": true
}

View File

@ -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>

View File

@ -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; }
}
}

View File

@ -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);
}
}

View File

@ -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>

View File

@ -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
}
}

8
src/JSInterop/README.md Normal file
View File

@ -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.

View File

@ -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>

View File

@ -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; }
}
}

View File

@ -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);
}
}
}

View File

@ -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
{
}
}
}

View File

@ -10,6 +10,7 @@ Microsoft.Extensions.Localization.IStringLocalizer&lt;T&gt;</Description>
<NoWarn>$(NoWarn);CS1591</NoWarn>
<GenerateDocumentationFile>true</GenerateDocumentationFile>
<PackageTags>localization</PackageTags>
<IsShipping>true</IsShipping>
</PropertyGroup>
</Project>

View File

@ -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);
}
}
}

View File

@ -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": []
}
]
}
]
}

View File

@ -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>

View File

@ -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; }
}
}

View File

@ -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}}'.");
}

View File

@ -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>

View File

@ -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")]

View File

@ -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;
}
}
}
}

View File

@ -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);
}
}
}

View File

@ -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": []
}
]
}

View File

@ -1,7 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFrameworks>netcoreapp2.2;net461</TargetFrameworks>
<TargetFrameworks>netcoreapp3.0;net472</TargetFrameworks>
</PropertyGroup>
<ItemGroup>

View File

@ -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;
}
}

View File

@ -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