mirror of
https://github.com/AlexMacocian/SystemExtensions.git
synced 2026-07-16 22:39:29 +00:00
Compare commits
1 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 1fae173b11 |
@@ -17,7 +17,7 @@ jobs:
|
|||||||
|
|
||||||
env:
|
env:
|
||||||
Configuration: Release
|
Configuration: Release
|
||||||
Solution_Path: SystemExtensions.slnx
|
Solution_Path: SystemExtensions.sln
|
||||||
Test_Project_Path: SystemExtensions.Tests\SystemExtensions.NetStandard.Tests.csproj
|
Test_Project_Path: SystemExtensions.Tests\SystemExtensions.NetStandard.Tests.csproj
|
||||||
DI_Test_Project_Path: SystemExtensions.DependencyInjection.Tests\SystemExtensions.NetStandard.DependencyInjection.Tests.csproj
|
DI_Test_Project_Path: SystemExtensions.DependencyInjection.Tests\SystemExtensions.NetStandard.DependencyInjection.Tests.csproj
|
||||||
Source_Project_Path: SystemExtensions.NetStandard\SystemExtensions.NetStandard.csproj
|
Source_Project_Path: SystemExtensions.NetStandard\SystemExtensions.NetStandard.csproj
|
||||||
@@ -33,7 +33,7 @@ jobs:
|
|||||||
- name: Install .NET Core
|
- name: Install .NET Core
|
||||||
uses: actions/setup-dotnet@v3
|
uses: actions/setup-dotnet@v3
|
||||||
with:
|
with:
|
||||||
dotnet-version: '10.0.x'
|
dotnet-version: '6.0.x'
|
||||||
|
|
||||||
- name: Setup MSBuild.exe
|
- name: Setup MSBuild.exe
|
||||||
uses: microsoft/setup-msbuild@v1.0.1
|
uses: microsoft/setup-msbuild@v1.0.1
|
||||||
@@ -55,9 +55,6 @@ jobs:
|
|||||||
- name: Build SystemExtensions.NetStandard.Security project
|
- name: Build SystemExtensions.NetStandard.Security project
|
||||||
run: dotnet build SystemExtensions.NetStandard.Security -c $env:Configuration
|
run: dotnet build SystemExtensions.NetStandard.Security -c $env:Configuration
|
||||||
|
|
||||||
- name: Build SystemExtensions.NetStandard.Generators project
|
|
||||||
run: dotnet build SystemExtensions.NetStandard.Generators -c $env:Configuration
|
|
||||||
|
|
||||||
- name: Package SystemExtensions.NetStandard
|
- name: Package SystemExtensions.NetStandard
|
||||||
run: dotnet pack -c Release -o . SystemExtensions.NetStandard\SystemExtensions.NetStandard.csproj
|
run: dotnet pack -c Release -o . SystemExtensions.NetStandard\SystemExtensions.NetStandard.csproj
|
||||||
|
|
||||||
@@ -70,8 +67,5 @@ jobs:
|
|||||||
- name: Package SystemExtensions.NetStandard.Security
|
- name: Package SystemExtensions.NetStandard.Security
|
||||||
run: dotnet pack -c Release -o . SystemExtensions.NetStandard.Security\SystemExtensions.NetStandard.Security.csproj
|
run: dotnet pack -c Release -o . SystemExtensions.NetStandard.Security\SystemExtensions.NetStandard.Security.csproj
|
||||||
|
|
||||||
- name: Package SystemExtensions.NetStandard.Generators
|
|
||||||
run: dotnet pack -c Release -o . SystemExtensions.NetStandard.Generators\SystemExtensions.NetStandard.Generators.csproj
|
|
||||||
|
|
||||||
- name: Publish
|
- name: Publish
|
||||||
run: dotnet nuget push *.nupkg --api-key ${{ secrets.NUGET_API_KEY }} --source https://api.nuget.org/v3/index.json --skip-duplicate
|
run: dotnet nuget push *.nupkg --api-key ${{ secrets.NUGET_API_KEY }} --source https://api.nuget.org/v3/index.json --skip-duplicate
|
||||||
@@ -19,7 +19,7 @@ jobs:
|
|||||||
runs-on: windows-latest
|
runs-on: windows-latest
|
||||||
|
|
||||||
env:
|
env:
|
||||||
Solution_Path: SystemExtensions.slnx
|
Solution_Path: SystemExtensions.sln
|
||||||
Test_Project_Path: SystemExtensions.Tests\SystemExtensions.NetStandard.Tests.csproj
|
Test_Project_Path: SystemExtensions.Tests\SystemExtensions.NetStandard.Tests.csproj
|
||||||
DI_Test_Project_Path: SystemExtensions.DependencyInjection.Tests\SystemExtensions.NetStandard.DependencyInjection.Tests.csproj
|
DI_Test_Project_Path: SystemExtensions.DependencyInjection.Tests\SystemExtensions.NetStandard.DependencyInjection.Tests.csproj
|
||||||
Sec_Test_Project_Path: SystemExtensions.NetStandard.Security.Tests\SystemExtensions.NetStandard.Security.Tests.csproj
|
Sec_Test_Project_Path: SystemExtensions.NetStandard.Security.Tests\SystemExtensions.NetStandard.Security.Tests.csproj
|
||||||
@@ -35,7 +35,7 @@ jobs:
|
|||||||
- name: Install .NET Core
|
- name: Install .NET Core
|
||||||
uses: actions/setup-dotnet@v3
|
uses: actions/setup-dotnet@v3
|
||||||
with:
|
with:
|
||||||
dotnet-version: '10.0.x'
|
dotnet-version: '6.0.x'
|
||||||
|
|
||||||
- name: Setup MSBuild.exe
|
- name: Setup MSBuild.exe
|
||||||
uses: microsoft/setup-msbuild@v1.0.1
|
uses: microsoft/setup-msbuild@v1.0.1
|
||||||
|
|||||||
+25
@@ -0,0 +1,25 @@
|
|||||||
|
using FluentAssertions;
|
||||||
|
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||||
|
using System.Configuration;
|
||||||
|
|
||||||
|
namespace SystemExtensions.DependencyInjection.Tests.Configuration;
|
||||||
|
|
||||||
|
[TestClass]
|
||||||
|
public class DefaultOptionsManagerTests
|
||||||
|
{
|
||||||
|
private readonly DefaultOptionsManager optionsManager = new();
|
||||||
|
|
||||||
|
[TestMethod]
|
||||||
|
public void GetOptions_ReturnsDefault()
|
||||||
|
{
|
||||||
|
var options = this.optionsManager.GetOptions<string>();
|
||||||
|
|
||||||
|
options.Should().BeNull();
|
||||||
|
}
|
||||||
|
|
||||||
|
[TestMethod]
|
||||||
|
public void UpdateOptions_Succeeds()
|
||||||
|
{
|
||||||
|
this.optionsManager.UpdateOptions(string.Empty);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
namespace SystemExtensions.DependencyInjection.Tests.Configuration;
|
||||||
|
|
||||||
|
public sealed class DummyOptions
|
||||||
|
{
|
||||||
|
}
|
||||||
@@ -0,0 +1,68 @@
|
|||||||
|
using FluentAssertions;
|
||||||
|
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||||
|
using Moq;
|
||||||
|
using System;
|
||||||
|
using System.Configuration;
|
||||||
|
|
||||||
|
namespace SystemExtensions.DependencyInjection.Tests.Configuration;
|
||||||
|
|
||||||
|
[TestClass]
|
||||||
|
public class LiveOptionsResolverTests
|
||||||
|
{
|
||||||
|
private readonly LiveOptionsResolver liveOptionsResolver = new();
|
||||||
|
private readonly Mock<Slim.IServiceProvider> serviceProviderMock = new();
|
||||||
|
private readonly Mock<IOptionsManager> optionsManagerMock = new();
|
||||||
|
|
||||||
|
[TestMethod]
|
||||||
|
public void CanResolve_ILiveOptions_ReturnsTrue()
|
||||||
|
{
|
||||||
|
var type = typeof(ILiveOptions<string>);
|
||||||
|
|
||||||
|
var canResolve = this.liveOptionsResolver.CanResolve(type);
|
||||||
|
|
||||||
|
canResolve.Should().BeTrue();
|
||||||
|
}
|
||||||
|
|
||||||
|
[TestMethod]
|
||||||
|
public void CanResolve_AnythingElse_ReturnsFalse()
|
||||||
|
{
|
||||||
|
var types = new Type[] { typeof(object), typeof(string), typeof(LiveOptionsResolverTests), typeof(int) };
|
||||||
|
|
||||||
|
foreach (var type in types)
|
||||||
|
{
|
||||||
|
var canResolve = this.liveOptionsResolver.CanResolve(type);
|
||||||
|
|
||||||
|
canResolve.Should().BeFalse();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[TestMethod]
|
||||||
|
public void Resolve_ILiveOptions_ReturnsILiveOptions()
|
||||||
|
{
|
||||||
|
this.SetupServiceProvider();
|
||||||
|
|
||||||
|
var liveOptions = this.liveOptionsResolver.Resolve(this.serviceProviderMock.Object, typeof(ILiveOptions<string>));
|
||||||
|
|
||||||
|
liveOptions.Should().BeAssignableTo<ILiveOptions<string>>();
|
||||||
|
}
|
||||||
|
|
||||||
|
[TestMethod]
|
||||||
|
public void Resolve_AnythingElse_Throws()
|
||||||
|
{
|
||||||
|
this.SetupServiceProvider();
|
||||||
|
|
||||||
|
Action action = new(() =>
|
||||||
|
{
|
||||||
|
this.liveOptionsResolver.Resolve(this.serviceProviderMock.Object, typeof(string));
|
||||||
|
});
|
||||||
|
|
||||||
|
action.Should().Throw<Exception>();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void SetupServiceProvider()
|
||||||
|
{
|
||||||
|
this.serviceProviderMock
|
||||||
|
.Setup(u => u.GetService<IOptionsManager>())
|
||||||
|
.Returns(this.optionsManagerMock.Object);
|
||||||
|
}
|
||||||
|
}
|
||||||
+68
@@ -0,0 +1,68 @@
|
|||||||
|
using FluentAssertions;
|
||||||
|
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||||
|
using Moq;
|
||||||
|
using System;
|
||||||
|
using System.Configuration;
|
||||||
|
|
||||||
|
namespace SystemExtensions.DependencyInjection.Tests.Configuration;
|
||||||
|
|
||||||
|
[TestClass]
|
||||||
|
public class LiveUpdateableOptionsResolverTests
|
||||||
|
{
|
||||||
|
private readonly LiveUpdateableOptionsResolver liveUpdateableOptionsResolver = new();
|
||||||
|
private readonly Mock<Slim.IServiceProvider> serviceProviderMock = new();
|
||||||
|
private readonly Mock<IOptionsManager> optionsManagerMock = new();
|
||||||
|
|
||||||
|
[TestMethod]
|
||||||
|
public void CanResolve_ILiveUpdateableOptions_ReturnsTrue()
|
||||||
|
{
|
||||||
|
var type = typeof(ILiveUpdateableOptions<string>);
|
||||||
|
|
||||||
|
var canResolve = this.liveUpdateableOptionsResolver.CanResolve(type);
|
||||||
|
|
||||||
|
canResolve.Should().BeTrue();
|
||||||
|
}
|
||||||
|
|
||||||
|
[TestMethod]
|
||||||
|
public void CanResolve_AnythingElse_ReturnsFalse()
|
||||||
|
{
|
||||||
|
var types = new Type[] { typeof(object), typeof(string), typeof(LiveOptionsResolverTests), typeof(int) };
|
||||||
|
|
||||||
|
foreach (var type in types)
|
||||||
|
{
|
||||||
|
var canResolve = this.liveUpdateableOptionsResolver.CanResolve(type);
|
||||||
|
|
||||||
|
canResolve.Should().BeFalse();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[TestMethod]
|
||||||
|
public void Resolve_ILiveUpdateableOptions_ReturnsILiveUpdateableOptions()
|
||||||
|
{
|
||||||
|
this.SetupServiceProvider();
|
||||||
|
|
||||||
|
var liveOptions = this.liveUpdateableOptionsResolver.Resolve(this.serviceProviderMock.Object, typeof(ILiveUpdateableOptions<string>));
|
||||||
|
|
||||||
|
liveOptions.Should().BeAssignableTo<ILiveUpdateableOptions<string>>();
|
||||||
|
}
|
||||||
|
|
||||||
|
[TestMethod]
|
||||||
|
public void Resolve_AnythingElse_Throws()
|
||||||
|
{
|
||||||
|
this.SetupServiceProvider();
|
||||||
|
|
||||||
|
Action action = new(() =>
|
||||||
|
{
|
||||||
|
this.liveUpdateableOptionsResolver.Resolve(this.serviceProviderMock.Object, typeof(string));
|
||||||
|
});
|
||||||
|
|
||||||
|
action.Should().Throw<Exception>();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void SetupServiceProvider()
|
||||||
|
{
|
||||||
|
this.serviceProviderMock
|
||||||
|
.Setup(u => u.GetService<IOptionsManager>())
|
||||||
|
.Returns(this.optionsManagerMock.Object);
|
||||||
|
}
|
||||||
|
}
|
||||||
+43
@@ -0,0 +1,43 @@
|
|||||||
|
using FluentAssertions;
|
||||||
|
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||||
|
using Moq;
|
||||||
|
using System.Configuration;
|
||||||
|
|
||||||
|
namespace SystemExtensions.DependencyInjection.Tests.Configuration;
|
||||||
|
|
||||||
|
[TestClass]
|
||||||
|
public class LiveUpdateableOptionsWrapperTests
|
||||||
|
{
|
||||||
|
private LiveUpdateableOptionsWrapper<string> optionsWrapper;
|
||||||
|
private readonly Mock<IOptionsManager> optionsManagerMock = new();
|
||||||
|
|
||||||
|
[TestInitialize]
|
||||||
|
public void TestInitialize()
|
||||||
|
{
|
||||||
|
this.optionsWrapper = new LiveUpdateableOptionsWrapper<string>(this.optionsManagerMock.Object);
|
||||||
|
}
|
||||||
|
|
||||||
|
[TestMethod]
|
||||||
|
public void GetValue_ReturnsValue()
|
||||||
|
{
|
||||||
|
this.optionsManagerMock
|
||||||
|
.Setup(u => u.GetOptions<string>())
|
||||||
|
.Returns("hello");
|
||||||
|
|
||||||
|
var value = this.optionsWrapper.Value;
|
||||||
|
|
||||||
|
value.Should().Be("hello");
|
||||||
|
}
|
||||||
|
|
||||||
|
[TestMethod]
|
||||||
|
public void UpdateOption_CallsOptionsManager()
|
||||||
|
{
|
||||||
|
this.optionsManagerMock
|
||||||
|
.Setup(u => u.UpdateOptions<string>(It.IsAny<string>()))
|
||||||
|
.Verifiable();
|
||||||
|
|
||||||
|
this.optionsWrapper.UpdateOption();
|
||||||
|
|
||||||
|
this.optionsManagerMock.Verify();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,70 @@
|
|||||||
|
using FluentAssertions;
|
||||||
|
using Microsoft.Extensions.Options;
|
||||||
|
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||||
|
using Moq;
|
||||||
|
using System;
|
||||||
|
using System.Configuration;
|
||||||
|
|
||||||
|
namespace SystemExtensions.DependencyInjection.Tests.Configuration;
|
||||||
|
|
||||||
|
[TestClass]
|
||||||
|
public class OptionsResolverTests
|
||||||
|
{
|
||||||
|
private readonly OptionsResolver optionsResolver = new();
|
||||||
|
private readonly Mock<Slim.IServiceProvider> serviceProviderMock = new();
|
||||||
|
|
||||||
|
public Mock<IOptionsManager> OptionsManagerMock { get; } = new();
|
||||||
|
|
||||||
|
[TestMethod]
|
||||||
|
public void CanResolve_ILiveOptions_ReturnsTrue()
|
||||||
|
{
|
||||||
|
var type = typeof(IOptions<string>);
|
||||||
|
|
||||||
|
var canResolve = this.optionsResolver.CanResolve(type);
|
||||||
|
|
||||||
|
canResolve.Should().BeTrue();
|
||||||
|
}
|
||||||
|
|
||||||
|
[TestMethod]
|
||||||
|
public void CanResolve_AnythingElse_ReturnsFalse()
|
||||||
|
{
|
||||||
|
var types = new Type[] { typeof(object), typeof(string), typeof(OptionsResolverTests), typeof(int) };
|
||||||
|
|
||||||
|
foreach (var type in types)
|
||||||
|
{
|
||||||
|
var canResolve = this.optionsResolver.CanResolve(type);
|
||||||
|
|
||||||
|
canResolve.Should().BeFalse();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[TestMethod]
|
||||||
|
public void Resolve_IOptions_ReturnsIOptions()
|
||||||
|
{
|
||||||
|
this.SetupServiceProvider();
|
||||||
|
|
||||||
|
var liveOptions = this.optionsResolver.Resolve(this.serviceProviderMock.Object, typeof(IOptions<string>));
|
||||||
|
|
||||||
|
liveOptions.Should().BeAssignableTo<IOptions<string>>();
|
||||||
|
}
|
||||||
|
|
||||||
|
[TestMethod]
|
||||||
|
public void Resolve_AnythingElse_Throws()
|
||||||
|
{
|
||||||
|
this.SetupServiceProvider();
|
||||||
|
|
||||||
|
Action action = new(() =>
|
||||||
|
{
|
||||||
|
this.optionsResolver.Resolve(this.serviceProviderMock.Object, typeof(string));
|
||||||
|
});
|
||||||
|
|
||||||
|
action.Should().Throw<Exception>();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void SetupServiceProvider()
|
||||||
|
{
|
||||||
|
this.serviceProviderMock
|
||||||
|
.Setup(u => u.GetService<IOptionsManager>())
|
||||||
|
.Returns(this.OptionsManagerMock.Object);
|
||||||
|
}
|
||||||
|
}
|
||||||
+68
@@ -0,0 +1,68 @@
|
|||||||
|
using FluentAssertions;
|
||||||
|
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||||
|
using Moq;
|
||||||
|
using System;
|
||||||
|
using System.Configuration;
|
||||||
|
|
||||||
|
namespace SystemExtensions.DependencyInjection.Tests.Configuration;
|
||||||
|
|
||||||
|
[TestClass]
|
||||||
|
public class UpdateableOptionsResolverTests
|
||||||
|
{
|
||||||
|
private readonly UpdateableOptionsResolver updateableOptionsResolver = new();
|
||||||
|
private readonly Mock<Slim.IServiceProvider> serviceProviderMock = new();
|
||||||
|
private readonly Mock<IOptionsManager> optionsManagerMock = new();
|
||||||
|
|
||||||
|
[TestMethod]
|
||||||
|
public void CanResolve_ILiveOptions_ReturnsTrue()
|
||||||
|
{
|
||||||
|
var type = typeof(IUpdateableOptions<string>);
|
||||||
|
|
||||||
|
var canResolve = this.updateableOptionsResolver.CanResolve(type);
|
||||||
|
|
||||||
|
canResolve.Should().BeTrue();
|
||||||
|
}
|
||||||
|
|
||||||
|
[TestMethod]
|
||||||
|
public void CanResolve_AnythingElse_ReturnsFalse()
|
||||||
|
{
|
||||||
|
var types = new Type[] { typeof(object), typeof(string), typeof(UpdateableOptionsResolverTests), typeof(int) };
|
||||||
|
|
||||||
|
foreach (var type in types)
|
||||||
|
{
|
||||||
|
var canResolve = this.updateableOptionsResolver.CanResolve(type);
|
||||||
|
|
||||||
|
canResolve.Should().BeFalse();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[TestMethod]
|
||||||
|
public void Resolve_IUpdateableOptions_ReturnsIUpdateableOptions()
|
||||||
|
{
|
||||||
|
this.SetupServiceProvider();
|
||||||
|
|
||||||
|
var liveOptions = this.updateableOptionsResolver.Resolve(this.serviceProviderMock.Object, typeof(IUpdateableOptions<string>));
|
||||||
|
|
||||||
|
liveOptions.Should().BeAssignableTo<IUpdateableOptions<string>>();
|
||||||
|
}
|
||||||
|
|
||||||
|
[TestMethod]
|
||||||
|
public void Resolve_AnythingElse_Throws()
|
||||||
|
{
|
||||||
|
this.SetupServiceProvider();
|
||||||
|
|
||||||
|
Action action = new(() =>
|
||||||
|
{
|
||||||
|
this.updateableOptionsResolver.Resolve(this.serviceProviderMock.Object, typeof(string));
|
||||||
|
});
|
||||||
|
|
||||||
|
action.Should().Throw<Exception>();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void SetupServiceProvider()
|
||||||
|
{
|
||||||
|
this.serviceProviderMock
|
||||||
|
.Setup(u => u.GetService<IOptionsManager>())
|
||||||
|
.Returns(this.optionsManagerMock.Object);
|
||||||
|
}
|
||||||
|
}
|
||||||
+47
@@ -0,0 +1,47 @@
|
|||||||
|
using FluentAssertions;
|
||||||
|
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||||
|
using Moq;
|
||||||
|
using System;
|
||||||
|
using System.Configuration;
|
||||||
|
using System.Extensions.Configuration;
|
||||||
|
|
||||||
|
namespace SystemExtensions.DependencyInjection.Tests.Configuration;
|
||||||
|
|
||||||
|
[TestClass]
|
||||||
|
public class UpdateableOptionsWrapperTests
|
||||||
|
{
|
||||||
|
private const string Value = "hello";
|
||||||
|
|
||||||
|
private UpdateableOptionsWrapper<string> optionsWrapper;
|
||||||
|
private readonly Mock<IOptionsManager> optionsManagerMock = new();
|
||||||
|
|
||||||
|
[TestInitialize]
|
||||||
|
public void TestInitialize()
|
||||||
|
{
|
||||||
|
this.optionsWrapper = new UpdateableOptionsWrapper<string>(this.optionsManagerMock.Object, Value);
|
||||||
|
}
|
||||||
|
|
||||||
|
[TestMethod]
|
||||||
|
public void GetValue_ReturnsValue()
|
||||||
|
{
|
||||||
|
this.optionsManagerMock
|
||||||
|
.Setup(u => u.GetOptions<string>())
|
||||||
|
.Throws<Exception>();
|
||||||
|
|
||||||
|
var value = this.optionsWrapper.Value;
|
||||||
|
|
||||||
|
value.Should().Be(Value);
|
||||||
|
}
|
||||||
|
|
||||||
|
[TestMethod]
|
||||||
|
public void UpdateOption_CallsOptionsManager()
|
||||||
|
{
|
||||||
|
this.optionsManagerMock
|
||||||
|
.Setup(u => u.UpdateOptions<string>(It.IsAny<string>()))
|
||||||
|
.Verifiable();
|
||||||
|
|
||||||
|
this.optionsWrapper.UpdateOption();
|
||||||
|
|
||||||
|
this.optionsManagerMock.Verify();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,9 +1,9 @@
|
|||||||
using FluentAssertions;
|
using FluentAssertions;
|
||||||
using Microsoft.Extensions.DependencyInjection;
|
|
||||||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||||
using NSubstitute;
|
using Moq;
|
||||||
using NSubstitute.ExceptionExtensions;
|
using Slim;
|
||||||
using System;
|
using System;
|
||||||
|
using System.DependencyInjection.Http;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Net.Http;
|
using System.Net.Http;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
@@ -18,12 +18,12 @@ public sealed class HttpClientBuilderTests
|
|||||||
private const string SomeValue = "SomeValue";
|
private const string SomeValue = "SomeValue";
|
||||||
|
|
||||||
private readonly HttpClientBuilder<object> httpClientBuilder;
|
private readonly HttpClientBuilder<object> httpClientBuilder;
|
||||||
private readonly IServiceCollection serviceProducerMock = Substitute.For<IServiceCollection>();
|
private readonly Mock<IServiceProducer> serviceProducerMock = new();
|
||||||
private readonly Uri baseAddress = new("http://contoso.co");
|
private readonly Uri baseAddress = new("http://contoso.co");
|
||||||
|
|
||||||
public HttpClientBuilderTests()
|
public HttpClientBuilderTests()
|
||||||
{
|
{
|
||||||
this.httpClientBuilder = new HttpClientBuilder<object>(this.serviceProducerMock);
|
this.httpClientBuilder = new HttpClientBuilder<object>(this.serviceProducerMock.Object);
|
||||||
}
|
}
|
||||||
|
|
||||||
[TestMethod]
|
[TestMethod]
|
||||||
@@ -63,13 +63,116 @@ public sealed class HttpClientBuilderTests
|
|||||||
{
|
{
|
||||||
var producer = this.httpClientBuilder.Build();
|
var producer = this.httpClientBuilder.Build();
|
||||||
|
|
||||||
producer.Should().BeEquivalentTo(this.serviceProducerMock);
|
producer.Should().Be(this.serviceProducerMock.Object);
|
||||||
|
}
|
||||||
|
|
||||||
|
[TestMethod]
|
||||||
|
public void Build_RegistersWithServiceProducer()
|
||||||
|
{
|
||||||
|
this.serviceProducerMock.Setup(u => u.RegisterScoped(It.IsAny<Func<Slim.IServiceProvider, IHttpClient<object>>>(), false));
|
||||||
|
|
||||||
|
this.httpClientBuilder.Build();
|
||||||
|
|
||||||
|
this.serviceProducerMock.Verify();
|
||||||
|
}
|
||||||
|
|
||||||
|
[TestMethod]
|
||||||
|
public void Build_CreatesExpectedClient()
|
||||||
|
{
|
||||||
|
this.serviceProducerMock.Setup(u => u.RegisterScoped(It.IsAny<Func<Slim.IServiceProvider, IHttpClient<object>>>(), false))
|
||||||
|
.Callback<Func<Slim.IServiceProvider, IHttpClient<object>>, bool>((factory, _) =>
|
||||||
|
{
|
||||||
|
var client = factory(new Mock<Slim.IServiceProvider>().Object);
|
||||||
|
client.BaseAddress.Should().BeNull();
|
||||||
|
client.DefaultRequestHeaders.Should().BeEmpty();
|
||||||
|
client.MaxResponseContentBufferSize.Should().Be(2147483647L);
|
||||||
|
client.Timeout.Should().Be(TimeSpan.FromSeconds(100));
|
||||||
|
});
|
||||||
|
|
||||||
|
this.httpClientBuilder.Build();
|
||||||
|
}
|
||||||
|
|
||||||
|
[TestMethod]
|
||||||
|
public void Build_WithBaseAddress_ReturnsClientWithBaseAddress()
|
||||||
|
{
|
||||||
|
this.serviceProducerMock.Setup(u => u.RegisterScoped(It.IsAny<Func<Slim.IServiceProvider, IHttpClient<object>>>(), false))
|
||||||
|
.Callback<Func<Slim.IServiceProvider, IHttpClient<object>>, bool>((factory, _) =>
|
||||||
|
{
|
||||||
|
var client = factory(new Mock<Slim.IServiceProvider>().Object);
|
||||||
|
client.BaseAddress.Should().Be(this.baseAddress);
|
||||||
|
});
|
||||||
|
|
||||||
|
this.httpClientBuilder.WithBaseAddress(this.baseAddress)
|
||||||
|
.Build();
|
||||||
|
}
|
||||||
|
|
||||||
|
[TestMethod]
|
||||||
|
public void Build_WithDefaultRequestHeaders_CallsFactory()
|
||||||
|
{
|
||||||
|
this.serviceProducerMock.Setup(u => u.RegisterScoped(It.IsAny<Func<Slim.IServiceProvider, IHttpClient<object>>>(), false))
|
||||||
|
.Callback<Func<Slim.IServiceProvider, IHttpClient<object>>, bool>((factory, _) =>
|
||||||
|
{
|
||||||
|
var client = factory(new Mock<Slim.IServiceProvider>().Object);
|
||||||
|
client.DefaultRequestHeaders.TryGetValues(SomeHeader, out var values);
|
||||||
|
|
||||||
|
values.Should().HaveCount(1);
|
||||||
|
values.FirstOrDefault().Should().Be(SomeValue);
|
||||||
|
});
|
||||||
|
|
||||||
|
this.httpClientBuilder.WithDefaultRequestHeadersSetup((headers) =>
|
||||||
|
{
|
||||||
|
headers.TryAddWithoutValidation(SomeHeader, SomeValue);
|
||||||
|
}).Build();
|
||||||
|
}
|
||||||
|
|
||||||
|
[TestMethod]
|
||||||
|
public void Build_WithMaxResponseBufferSize_ReturnsClientWithMaxResponseBufferSize()
|
||||||
|
{
|
||||||
|
this.serviceProducerMock.Setup(u => u.RegisterScoped(It.IsAny<Func<Slim.IServiceProvider, IHttpClient<object>>>(), false))
|
||||||
|
.Callback<Func<Slim.IServiceProvider, IHttpClient<object>>, bool>((factory, _) =>
|
||||||
|
{
|
||||||
|
var client = factory(new Mock<Slim.IServiceProvider>().Object);
|
||||||
|
client.MaxResponseContentBufferSize.Should().Be(100);
|
||||||
|
});
|
||||||
|
|
||||||
|
this.httpClientBuilder.WithMaxResponseBufferSize(100)
|
||||||
|
.Build();
|
||||||
|
}
|
||||||
|
|
||||||
|
[TestMethod]
|
||||||
|
public void Build_WithTimeout_ReturnsClientWithTimeout()
|
||||||
|
{
|
||||||
|
this.serviceProducerMock.Setup(u => u.RegisterScoped(It.IsAny<Func<Slim.IServiceProvider, IHttpClient<object>>>(), false))
|
||||||
|
.Callback<Func<Slim.IServiceProvider, IHttpClient<object>>, bool>((factory, _) =>
|
||||||
|
{
|
||||||
|
var client = factory(new Mock<Slim.IServiceProvider>().Object);
|
||||||
|
client.Timeout.Should().Be(TimeSpan.FromSeconds(5));
|
||||||
|
});
|
||||||
|
|
||||||
|
this.httpClientBuilder.WithTimeout(TimeSpan.FromSeconds(5))
|
||||||
|
.Build();
|
||||||
|
}
|
||||||
|
|
||||||
|
[TestMethod]
|
||||||
|
public void Build_WithMessageHandler_CallsMessageHandler()
|
||||||
|
{
|
||||||
|
var handlerMock = new HttpMessageHandlerMock();
|
||||||
|
this.serviceProducerMock.Setup(u => u.RegisterScoped(It.IsAny<Func<Slim.IServiceProvider, IHttpClient<object>>>(), false))
|
||||||
|
.Callback<Func<Slim.IServiceProvider, IHttpClient<object>>, bool>(async (factory, _) =>
|
||||||
|
{
|
||||||
|
var client = factory(new Mock<Slim.IServiceProvider>().Object);
|
||||||
|
await client.GetAsync(this.baseAddress);
|
||||||
|
handlerMock.Called.Should().BeTrue();
|
||||||
|
});
|
||||||
|
|
||||||
|
this.httpClientBuilder.WithMessageHandler(sp => handlerMock)
|
||||||
|
.Build();
|
||||||
}
|
}
|
||||||
|
|
||||||
[TestMethod]
|
[TestMethod]
|
||||||
public async Task HttpClientBuilder_RegistersClientCorrectly_IServiceProviderReturnsExpected()
|
public async Task HttpClientBuilder_RegistersClientCorrectly_IServiceProviderReturnsExpected()
|
||||||
{
|
{
|
||||||
var container = new ServiceCollection();
|
var container = new ServiceManager();
|
||||||
var messageHandler = new HttpMessageHandlerMock();
|
var messageHandler = new HttpMessageHandlerMock();
|
||||||
new HttpClientBuilder<object>(container)
|
new HttpClientBuilder<object>(container)
|
||||||
.WithBaseAddress(this.baseAddress)
|
.WithBaseAddress(this.baseAddress)
|
||||||
@@ -79,8 +182,7 @@ public sealed class HttpClientBuilderTests
|
|||||||
.WithTimeout(TimeSpan.FromSeconds(5))
|
.WithTimeout(TimeSpan.FromSeconds(5))
|
||||||
.Build();
|
.Build();
|
||||||
|
|
||||||
var di = container.BuildServiceProvider();
|
var client = container.GetService<IHttpClient<object>>();
|
||||||
var client = di.GetService<IHttpClient<object>>();
|
|
||||||
await client.GetAsync("");
|
await client.GetAsync("");
|
||||||
|
|
||||||
client.Should().NotBeNull();
|
client.Should().NotBeNull();
|
||||||
|
|||||||
@@ -0,0 +1,68 @@
|
|||||||
|
using FluentAssertions;
|
||||||
|
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||||
|
using Moq;
|
||||||
|
using System;
|
||||||
|
using System.Http;
|
||||||
|
using System.Net.Http;
|
||||||
|
|
||||||
|
namespace SystemExtensions.DependencyInjection.Tests.Http;
|
||||||
|
|
||||||
|
[TestClass]
|
||||||
|
public class HttpClientResolverTests
|
||||||
|
{
|
||||||
|
private readonly HttpClientResolver httpClientResolver = new();
|
||||||
|
private readonly Mock<Slim.IServiceProvider> serviceProviderMock = new();
|
||||||
|
|
||||||
|
[TestMethod]
|
||||||
|
public void CanResolve_IHttpClient_ReturnsTrue()
|
||||||
|
{
|
||||||
|
var type = typeof(IHttpClient<>);
|
||||||
|
|
||||||
|
var canResolve = this.httpClientResolver.CanResolve(type);
|
||||||
|
|
||||||
|
canResolve.Should().BeTrue();
|
||||||
|
}
|
||||||
|
|
||||||
|
[TestMethod]
|
||||||
|
public void CanResolve_AnythingElse_ReturnsFalse()
|
||||||
|
{
|
||||||
|
var types = new Type[] { typeof(HttpClient), typeof(object), typeof(string), typeof(HttpClientResolverTests), typeof(int) };
|
||||||
|
|
||||||
|
foreach (var type in types)
|
||||||
|
{
|
||||||
|
var canResolve = this.httpClientResolver.CanResolve(type);
|
||||||
|
|
||||||
|
canResolve.Should().BeFalse();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[TestMethod]
|
||||||
|
public void Resolve_TypedClient_ReturnsIHttpClient()
|
||||||
|
{
|
||||||
|
var client = this.httpClientResolver.Resolve(this.serviceProviderMock.Object, typeof(IHttpClient<string>));
|
||||||
|
|
||||||
|
client.Should().BeAssignableTo<IHttpClient<string>>();
|
||||||
|
}
|
||||||
|
|
||||||
|
[TestMethod]
|
||||||
|
public void Resolve_NonGenericType_Throws()
|
||||||
|
{
|
||||||
|
Action action = new(() =>
|
||||||
|
{
|
||||||
|
this.httpClientResolver.Resolve(this.serviceProviderMock.Object, typeof(IHttpClient<>));
|
||||||
|
});
|
||||||
|
|
||||||
|
action.Should().Throw<Exception>();
|
||||||
|
}
|
||||||
|
|
||||||
|
[TestMethod]
|
||||||
|
public void Resolve_RandomType_Throws()
|
||||||
|
{
|
||||||
|
Action action = new(() =>
|
||||||
|
{
|
||||||
|
this.httpClientResolver.Resolve(this.serviceProviderMock.Object, typeof(string));
|
||||||
|
});
|
||||||
|
|
||||||
|
action.Should().Throw<Exception>();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,42 @@
|
|||||||
|
using FluentAssertions;
|
||||||
|
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||||
|
using Moq;
|
||||||
|
using System.Logging;
|
||||||
|
|
||||||
|
namespace SystemExtensions.DependencyInjection.Tests.Logging;
|
||||||
|
|
||||||
|
[TestClass]
|
||||||
|
public class CVLoggerProviderTests
|
||||||
|
{
|
||||||
|
private readonly Mock<ILogsWriter> logsWriterMock = new();
|
||||||
|
private CVLoggerProvider cVLoggerProvider;
|
||||||
|
|
||||||
|
[TestInitialize]
|
||||||
|
public void TestInitialize()
|
||||||
|
{
|
||||||
|
this.cVLoggerProvider = new CVLoggerProvider(this.logsWriterMock.Object);
|
||||||
|
}
|
||||||
|
|
||||||
|
[TestMethod]
|
||||||
|
public void CreateLogger_CreatesNewLogger()
|
||||||
|
{
|
||||||
|
var logger = this.cVLoggerProvider.CreateLogger(string.Empty);
|
||||||
|
|
||||||
|
logger.Should().NotBeNull();
|
||||||
|
}
|
||||||
|
|
||||||
|
[TestMethod]
|
||||||
|
public void LogEntry_CallsLogWriter()
|
||||||
|
{
|
||||||
|
this.cVLoggerProvider.LogEntry(new Log());
|
||||||
|
|
||||||
|
this.logsWriterMock.Verify();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void SetupLogsWriter()
|
||||||
|
{
|
||||||
|
this.logsWriterMock
|
||||||
|
.Setup(u => u.WriteLog(It.IsAny<Log>()))
|
||||||
|
.Verifiable();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,75 @@
|
|||||||
|
using FluentAssertions;
|
||||||
|
using Microsoft.Extensions.Logging;
|
||||||
|
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||||
|
using Moq;
|
||||||
|
using System;
|
||||||
|
using System.Logging;
|
||||||
|
|
||||||
|
namespace SystemExtensions.DependencyInjection.Tests.Logging;
|
||||||
|
|
||||||
|
[TestClass]
|
||||||
|
public class CVLoggerTests
|
||||||
|
{
|
||||||
|
private readonly Mock<ICVLoggerProvider> cvLoggerProviderMock = new();
|
||||||
|
private CVLogger cVLogger;
|
||||||
|
|
||||||
|
[TestInitialize]
|
||||||
|
public void TestInitialize()
|
||||||
|
{
|
||||||
|
this.cVLogger = new CVLogger(string.Empty, this.cvLoggerProviderMock.Object);
|
||||||
|
}
|
||||||
|
|
||||||
|
[TestMethod]
|
||||||
|
public void BeginScope_ReturnsNull()
|
||||||
|
{
|
||||||
|
var scope = this.cVLogger.BeginScope(string.Empty);
|
||||||
|
|
||||||
|
scope.Should().BeNull();
|
||||||
|
}
|
||||||
|
|
||||||
|
[TestMethod]
|
||||||
|
[DataRow(LogLevel.Debug)]
|
||||||
|
[DataRow(LogLevel.Trace)]
|
||||||
|
[DataRow(LogLevel.Information)]
|
||||||
|
[DataRow(LogLevel.Warning)]
|
||||||
|
[DataRow(LogLevel.Error)]
|
||||||
|
[DataRow(LogLevel.Critical)]
|
||||||
|
public void IsEnabled_OnAllLogLevels_ReturnsTrue(LogLevel logLevel)
|
||||||
|
{
|
||||||
|
var enabled = this.cVLogger.IsEnabled(logLevel);
|
||||||
|
|
||||||
|
enabled.Should().BeTrue();
|
||||||
|
}
|
||||||
|
|
||||||
|
[TestMethod]
|
||||||
|
public void Log_CallsFormatter()
|
||||||
|
{
|
||||||
|
var called = false;
|
||||||
|
Func<string, Exception, string> messageFormatter = new((state, exception) =>
|
||||||
|
{
|
||||||
|
called = true;
|
||||||
|
return string.Empty;
|
||||||
|
});
|
||||||
|
|
||||||
|
this.cVLogger.Log(LogLevel.Debug, new EventId(), "Some message", new Exception(), messageFormatter);
|
||||||
|
|
||||||
|
called.Should().BeTrue();
|
||||||
|
}
|
||||||
|
|
||||||
|
[TestMethod]
|
||||||
|
public void Log_CallsLogsProvider()
|
||||||
|
{
|
||||||
|
this.SetupLoggerProvider();
|
||||||
|
|
||||||
|
this.cVLogger.Log(LogLevel.Debug, new EventId(), "Some message", new Exception(), new Func<string, Exception, string>((s, e) => string.Empty));
|
||||||
|
|
||||||
|
this.cvLoggerProviderMock.Verify();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void SetupLoggerProvider()
|
||||||
|
{
|
||||||
|
this.cvLoggerProviderMock
|
||||||
|
.Setup(u => u.LogEntry(It.IsAny<Log>()))
|
||||||
|
.Verifiable();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,29 @@
|
|||||||
|
using FluentAssertions;
|
||||||
|
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||||
|
using System;
|
||||||
|
using System.Logging;
|
||||||
|
|
||||||
|
namespace SystemExtensions.DependencyInjection.Tests.Logging;
|
||||||
|
|
||||||
|
[TestClass]
|
||||||
|
public class DebugLogsWriterTests
|
||||||
|
{
|
||||||
|
private readonly DebugLogsWriter logsWriter = new();
|
||||||
|
|
||||||
|
[TestMethod]
|
||||||
|
public void WriteLog_Succeeds()
|
||||||
|
{
|
||||||
|
this.logsWriter.WriteLog(new Log());
|
||||||
|
}
|
||||||
|
|
||||||
|
[TestMethod]
|
||||||
|
public void WriteNullLog_Throws()
|
||||||
|
{
|
||||||
|
Action action = new(() =>
|
||||||
|
{
|
||||||
|
this.logsWriter.WriteLog(null);
|
||||||
|
});
|
||||||
|
|
||||||
|
action.Should().Throw<Exception>();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,107 @@
|
|||||||
|
using FluentAssertions;
|
||||||
|
using Microsoft.Extensions.Logging;
|
||||||
|
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||||
|
using Moq;
|
||||||
|
using System;
|
||||||
|
using System.Logging;
|
||||||
|
|
||||||
|
namespace SystemExtensions.DependencyInjection.Tests.Logging;
|
||||||
|
|
||||||
|
[TestClass]
|
||||||
|
public class LoggerResolverTests
|
||||||
|
{
|
||||||
|
private readonly Mock<Slim.IServiceProvider> serviceProviderMock = new();
|
||||||
|
private readonly Mock<ILoggerFactory> loggerFactoryMock = new();
|
||||||
|
private readonly Mock<ILogger> loggerMock = new();
|
||||||
|
private readonly LoggerResolver loggerResolver = new();
|
||||||
|
|
||||||
|
[TestMethod]
|
||||||
|
public void CanResolve_ILogger_ReturnsTrue()
|
||||||
|
{
|
||||||
|
var type = typeof(ILogger);
|
||||||
|
|
||||||
|
var canResolve = this.loggerResolver.CanResolve(type);
|
||||||
|
|
||||||
|
canResolve.Should().BeTrue();
|
||||||
|
}
|
||||||
|
|
||||||
|
[TestMethod]
|
||||||
|
public void CanResolve_GenericILogger_ReturnsTrue()
|
||||||
|
{
|
||||||
|
var type = typeof(ILogger<string>);
|
||||||
|
|
||||||
|
var canResolve = this.loggerResolver.CanResolve(type);
|
||||||
|
|
||||||
|
canResolve.Should().BeTrue();
|
||||||
|
}
|
||||||
|
|
||||||
|
[TestMethod]
|
||||||
|
public void CanResolve_AnythingElse_ReturnsFalse()
|
||||||
|
{
|
||||||
|
var types = new Type[] { typeof(CVLogger), typeof(object), typeof(string), typeof(LoggerResolverTests), typeof(int) };
|
||||||
|
|
||||||
|
foreach(var type in types)
|
||||||
|
{
|
||||||
|
var canResolve = this.loggerResolver.CanResolve(type);
|
||||||
|
|
||||||
|
canResolve.Should().BeFalse();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[TestMethod]
|
||||||
|
public void Resolve_ILogger_ReturnsILogger()
|
||||||
|
{
|
||||||
|
this.SetupIServiceProvider();
|
||||||
|
|
||||||
|
var logger = this.loggerResolver.Resolve(this.serviceProviderMock.Object, typeof(ILogger));
|
||||||
|
|
||||||
|
logger.Should().BeAssignableTo<ILogger>();
|
||||||
|
}
|
||||||
|
|
||||||
|
[TestMethod]
|
||||||
|
public void Resolve_TypedILogger_ReturnsTypedILogger()
|
||||||
|
{
|
||||||
|
this.SetupIServiceProvider();
|
||||||
|
|
||||||
|
var logger = this.loggerResolver.Resolve(this.serviceProviderMock.Object, typeof(ILogger<string>));
|
||||||
|
|
||||||
|
logger.Should().BeAssignableTo<ILogger<string>>();
|
||||||
|
}
|
||||||
|
|
||||||
|
[TestMethod]
|
||||||
|
public void Resolve_RandomType_Throws()
|
||||||
|
{
|
||||||
|
this.SetupIServiceProvider();
|
||||||
|
|
||||||
|
Action action = new(() =>
|
||||||
|
{
|
||||||
|
this.loggerResolver.Resolve(this.serviceProviderMock.Object, typeof(string));
|
||||||
|
});
|
||||||
|
|
||||||
|
action.Should().Throw<Exception>();
|
||||||
|
}
|
||||||
|
|
||||||
|
[TestMethod]
|
||||||
|
public void Resolve_NonTypedGeneric_Throws()
|
||||||
|
{
|
||||||
|
this.SetupIServiceProvider();
|
||||||
|
|
||||||
|
Action action = new(() =>
|
||||||
|
{
|
||||||
|
this.loggerResolver.Resolve(this.serviceProviderMock.Object, typeof(ILogger<>));
|
||||||
|
});
|
||||||
|
|
||||||
|
action.Should().Throw<Exception>();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void SetupIServiceProvider()
|
||||||
|
{
|
||||||
|
this.serviceProviderMock
|
||||||
|
.Setup(u => u.GetService<ILoggerFactory>())
|
||||||
|
.Returns(this.loggerFactoryMock.Object);
|
||||||
|
|
||||||
|
this.loggerFactoryMock
|
||||||
|
.Setup(u => u.CreateLogger(It.IsAny<string>()))
|
||||||
|
.Returns(this.loggerMock.Object);
|
||||||
|
}
|
||||||
|
}
|
||||||
+7
-11
@@ -1,22 +1,18 @@
|
|||||||
<Project Sdk="Microsoft.NET.Sdk">
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<TargetFramework>net10.0</TargetFramework>
|
<TargetFramework>net6.0</TargetFramework>
|
||||||
|
|
||||||
<IsPackable>false</IsPackable>
|
<IsPackable>false</IsPackable>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="FluentAssertions" Version="6.12.0" />
|
<PackageReference Include="FluentAssertions" Version="6.9.0" />
|
||||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.11.0" />
|
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.3.2" />
|
||||||
<PackageReference Include="MSTest.TestAdapter" Version="3.5.2" />
|
<PackageReference Include="Moq" Version="4.18.2" />
|
||||||
<PackageReference Include="MSTest.TestFramework" Version="3.5.2" />
|
<PackageReference Include="MSTest.TestAdapter" Version="2.2.10" />
|
||||||
<PackageReference Include="coverlet.collector" Version="6.0.2">
|
<PackageReference Include="MSTest.TestFramework" Version="2.2.10" />
|
||||||
<PrivateAssets>all</PrivateAssets>
|
<PackageReference Include="coverlet.collector" Version="3.1.2">
|
||||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
|
||||||
</PackageReference>
|
|
||||||
<PackageReference Include="NSubstitute" Version="5.1.0" />
|
|
||||||
<PackageReference Include="NSubstitute.Analyzers.CSharp" Version="1.0.17">
|
|
||||||
<PrivateAssets>all</PrivateAssets>
|
<PrivateAssets>all</PrivateAssets>
|
||||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||||
</PackageReference>
|
</PackageReference>
|
||||||
|
|||||||
@@ -1,70 +0,0 @@
|
|||||||
using FluentAssertions;
|
|
||||||
using global::SystemExtensions.NetStandard.DependencyInjection.Tests.Http.Models;
|
|
||||||
using Microsoft.Extensions.DependencyInjection;
|
|
||||||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
|
||||||
using NSubstitute;
|
|
||||||
using System;
|
|
||||||
using System.Extensions;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Net.Http;
|
|
||||||
using System.Net.WebSockets;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
|
|
||||||
namespace SystemExtensions.NetStandard.DependencyInjection.Tests.WebSockets;
|
|
||||||
[TestClass]
|
|
||||||
public sealed class HttpClientBuilderTests
|
|
||||||
{
|
|
||||||
private readonly ClientWebSocketBuilder<object> clientWebSocketBuilder;
|
|
||||||
private readonly IServiceCollection serviceProducerMock = Substitute.For<IServiceCollection>();
|
|
||||||
|
|
||||||
public HttpClientBuilderTests()
|
|
||||||
{
|
|
||||||
this.clientWebSocketBuilder = new ClientWebSocketBuilder<object>(this.serviceProducerMock);
|
|
||||||
}
|
|
||||||
|
|
||||||
[TestMethod]
|
|
||||||
public void Constructor_NullServiceProducer_Throws()
|
|
||||||
{
|
|
||||||
var action = () => new ClientWebSocketBuilder<object>(null);
|
|
||||||
|
|
||||||
action.Should().Throw<ArgumentNullException>();
|
|
||||||
}
|
|
||||||
|
|
||||||
[TestMethod]
|
|
||||||
public void WithInnerWebSocket_NullHandler_Throws()
|
|
||||||
{
|
|
||||||
var action = () => this.clientWebSocketBuilder.WithInnerWebSocket(null);
|
|
||||||
|
|
||||||
action.Should().Throw<ArgumentNullException>();
|
|
||||||
}
|
|
||||||
|
|
||||||
[TestMethod]
|
|
||||||
public void WIthInnerWebSocketFactory_NullBaseAddress_Throws()
|
|
||||||
{
|
|
||||||
var action = () => this.clientWebSocketBuilder.WithInnerWebSocketFactory(null);
|
|
||||||
|
|
||||||
action.Should().Throw<ArgumentNullException>();
|
|
||||||
}
|
|
||||||
|
|
||||||
[TestMethod]
|
|
||||||
public void Build_ReturnsServiceProducer()
|
|
||||||
{
|
|
||||||
var producer = this.clientWebSocketBuilder.Build();
|
|
||||||
|
|
||||||
producer.Should().BeEquivalentTo(this.serviceProducerMock);
|
|
||||||
}
|
|
||||||
|
|
||||||
[TestMethod]
|
|
||||||
public async Task ClientWebSocketBuilder_RegistersClientCorrectly_IServiceProviderReturnsExpected()
|
|
||||||
{
|
|
||||||
var container = new ServiceCollection();
|
|
||||||
var innerWebSocket = new ClientWebSocket();
|
|
||||||
container.RegisterClientWebSocket<object>()
|
|
||||||
.WithInnerWebSocket(innerWebSocket)
|
|
||||||
.Build();
|
|
||||||
|
|
||||||
var di = container.BuildServiceProvider();
|
|
||||||
var client = di.GetService<IClientWebSocket<object>>();
|
|
||||||
client.Should().NotBeNull();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,17 +0,0 @@
|
|||||||
using Microsoft.Extensions.Logging;
|
|
||||||
using System.Logging;
|
|
||||||
using System.Runtime.CompilerServices;
|
|
||||||
|
|
||||||
namespace System.Extensions.Core;
|
|
||||||
public static class LoggingExtensions
|
|
||||||
{
|
|
||||||
public static ScopedLogger<T> CreateScopedLogger<T>(this ILogger<T> logger, string flowIdentifier, [CallerMemberName] string? methodName = default)
|
|
||||||
{
|
|
||||||
return ScopedLogger<T>.Create(logger, methodName ?? string.Empty, flowIdentifier);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static ScopedLogger<T> CreateScopedLogger<T>(this ILogger<T> logger, [CallerMemberName] string? methodName = default)
|
|
||||||
{
|
|
||||||
return ScopedLogger<T>.Create(logger, methodName ?? string.Empty, string.Empty);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,11 +1,10 @@
|
|||||||
using System.Diagnostics.CodeAnalysis;
|
using System.Runtime.CompilerServices;
|
||||||
using System.Runtime.CompilerServices;
|
|
||||||
|
|
||||||
namespace System.Core.Extensions;
|
namespace System.Core.Extensions;
|
||||||
|
|
||||||
public static class ObjectExtensions
|
public static class ObjectExtensions
|
||||||
{
|
{
|
||||||
public static T ThrowIfNull<T>([NotNull] this T? obj, [CallerArgumentExpression("obj")] string? paramName = null)
|
public static T ThrowIfNull<T>([ValidatedNotNull] this T obj, [CallerArgumentExpression("obj")] string? paramName = null)
|
||||||
where T : class
|
where T : class
|
||||||
{
|
{
|
||||||
if (obj is null)
|
if (obj is null)
|
||||||
@@ -15,4 +14,9 @@ public static class ObjectExtensions
|
|||||||
|
|
||||||
return obj;
|
return obj;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[AttributeUsage(AttributeTargets.Parameter)]
|
||||||
|
sealed class ValidatedNotNullAttribute : Attribute
|
||||||
|
{
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,17 +1,17 @@
|
|||||||
<Project Sdk="Microsoft.NET.Sdk">
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<TargetFramework>net10.0</TargetFramework>
|
<TargetFramework>net6.0</TargetFramework>
|
||||||
<ImplicitUsings>enable</ImplicitUsings>
|
<ImplicitUsings>enable</ImplicitUsings>
|
||||||
<GeneratePackageOnBuild>true</GeneratePackageOnBuild>
|
<GeneratePackageOnBuild>true</GeneratePackageOnBuild>
|
||||||
<Nullable>enable</Nullable>
|
<Nullable>enable</Nullable>
|
||||||
<RootNamespace>System</RootNamespace>
|
<RootNamespace>System</RootNamespace>
|
||||||
<PackageLicenseFile>LICENSE</PackageLicenseFile>
|
<PackageLicenseFile>LICENSE</PackageLicenseFile>
|
||||||
<PackageRequireLicenseAcceptance>true</PackageRequireLicenseAcceptance>
|
<PackageRequireLicenseAcceptance>true</PackageRequireLicenseAcceptance>
|
||||||
<Version>1.9</Version>
|
<Version>1.0.1</Version>
|
||||||
<Authors>Alexandru Macocian</Authors>
|
<Authors>Alexandru Macocian</Authors>
|
||||||
<RepositoryUrl>https://github.com/AlexMacocian/SystemExtensions</RepositoryUrl>
|
<RepositoryUrl>https://github.com/AlexMacocian/SystemExtensions</RepositoryUrl>
|
||||||
<Description>Extensions for the System namespace</Description>
|
<Description>Extensions for the System namespace</Description>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
@@ -21,7 +21,4 @@
|
|||||||
</None>
|
</None>
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
|
||||||
<ProjectReference Include="..\SystemExtensions.NetStandard\SystemExtensions.NetStandard.csproj" />
|
|
||||||
</ItemGroup>
|
|
||||||
</Project>
|
</Project>
|
||||||
|
|||||||
+13
@@ -0,0 +1,13 @@
|
|||||||
|
namespace System.Configuration;
|
||||||
|
|
||||||
|
public sealed class DefaultOptionsManager : IOptionsManager
|
||||||
|
{
|
||||||
|
public T GetOptions<T>() where T : class
|
||||||
|
{
|
||||||
|
return default;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void UpdateOptions<T>(T value) where T : class
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
using Microsoft.Extensions.Options;
|
||||||
|
|
||||||
|
namespace System.Configuration;
|
||||||
|
|
||||||
|
public interface ILiveOptions<T> : IOptions<T>
|
||||||
|
where T : class
|
||||||
|
{
|
||||||
|
}
|
||||||
+6
@@ -0,0 +1,6 @@
|
|||||||
|
namespace System.Configuration;
|
||||||
|
|
||||||
|
public interface ILiveUpdateableOptions<T> : ILiveOptions<T>, IUpdateableOptions<T>
|
||||||
|
where T : class
|
||||||
|
{
|
||||||
|
}
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
namespace System.Configuration;
|
||||||
|
|
||||||
|
public interface IOptionsManager
|
||||||
|
{
|
||||||
|
T GetOptions<T>()
|
||||||
|
where T : class;
|
||||||
|
void UpdateOptions<T>(T value)
|
||||||
|
where T : class;
|
||||||
|
}
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
using Microsoft.Extensions.Options;
|
||||||
|
|
||||||
|
namespace System.Configuration;
|
||||||
|
|
||||||
|
public interface IUpdateableOptions<out T> : IOptions<T>
|
||||||
|
where T : class
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Updates the configuration with the current value stored in <see cref="IOptions{TOptions}.Value"/>.
|
||||||
|
/// </summary>
|
||||||
|
void UpdateOption();
|
||||||
|
}
|
||||||
@@ -0,0 +1,26 @@
|
|||||||
|
using Slim.Resolvers;
|
||||||
|
|
||||||
|
namespace System.Configuration;
|
||||||
|
|
||||||
|
public sealed class LiveOptionsResolver : IDependencyResolver
|
||||||
|
{
|
||||||
|
private static readonly Type optionsType = typeof(LiveUpdateableOptionsWrapper<>);
|
||||||
|
|
||||||
|
public bool CanResolve(Type type)
|
||||||
|
{
|
||||||
|
if (type.IsGenericType && type.GetGenericTypeDefinition() == typeof(ILiveOptions<>))
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public object Resolve(Slim.IServiceProvider serviceProvider, Type type)
|
||||||
|
{
|
||||||
|
var typedOptionsType = optionsType.MakeGenericType(type.GetGenericArguments());
|
||||||
|
var configurationManager = serviceProvider.GetService<IOptionsManager>();
|
||||||
|
|
||||||
|
return Activator.CreateInstance(typedOptionsType, configurationManager);
|
||||||
|
}
|
||||||
|
}
|
||||||
+25
@@ -0,0 +1,25 @@
|
|||||||
|
using Slim.Resolvers;
|
||||||
|
|
||||||
|
namespace System.Configuration;
|
||||||
|
|
||||||
|
public sealed class LiveUpdateableOptionsResolver : IDependencyResolver
|
||||||
|
{
|
||||||
|
private static readonly Type optionsType = typeof(LiveUpdateableOptionsWrapper<>);
|
||||||
|
|
||||||
|
public bool CanResolve(Type type)
|
||||||
|
{
|
||||||
|
if (type.IsGenericType && type.GetGenericTypeDefinition() == typeof(ILiveUpdateableOptions<>))
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public object Resolve(Slim.IServiceProvider serviceProvider, Type type)
|
||||||
|
{
|
||||||
|
var typedOptionsType = optionsType.MakeGenericType(type.GetGenericArguments());
|
||||||
|
var configurationManager = serviceProvider.GetService<IOptionsManager>();
|
||||||
|
return Activator.CreateInstance(typedOptionsType, configurationManager);
|
||||||
|
}
|
||||||
|
}
|
||||||
+30
@@ -0,0 +1,30 @@
|
|||||||
|
using System.Extensions;
|
||||||
|
|
||||||
|
namespace System.Configuration;
|
||||||
|
|
||||||
|
public sealed class LiveUpdateableOptionsWrapper<T> : ILiveUpdateableOptions<T>
|
||||||
|
where T : class
|
||||||
|
{
|
||||||
|
private readonly IOptionsManager configurationManager;
|
||||||
|
|
||||||
|
private T value;
|
||||||
|
|
||||||
|
public T Value
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
this.value = this.configurationManager.GetOptions<T>();
|
||||||
|
return this.value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public LiveUpdateableOptionsWrapper(IOptionsManager configurationManager)
|
||||||
|
{
|
||||||
|
this.configurationManager = configurationManager.ThrowIfNull(nameof(configurationManager));
|
||||||
|
}
|
||||||
|
|
||||||
|
public void UpdateOption()
|
||||||
|
{
|
||||||
|
this.configurationManager.UpdateOptions<T>(this.value);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,32 @@
|
|||||||
|
using Microsoft.Extensions.Options;
|
||||||
|
using Slim.Resolvers;
|
||||||
|
|
||||||
|
namespace System.Configuration;
|
||||||
|
|
||||||
|
public sealed class OptionsResolver : IDependencyResolver
|
||||||
|
{
|
||||||
|
private static readonly Type optionsType = typeof(OptionsWrapper<>);
|
||||||
|
private static readonly Type configurationType = typeof(IOptionsManager);
|
||||||
|
|
||||||
|
public bool CanResolve(Type type)
|
||||||
|
{
|
||||||
|
if (type.IsGenericType && type.GetGenericTypeDefinition() == typeof(IOptions<>))
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public object Resolve(Slim.IServiceProvider serviceProvider, Type type)
|
||||||
|
{
|
||||||
|
var typedOptionsType = optionsType.MakeGenericType(type.GetGenericArguments());
|
||||||
|
var configurationManager = serviceProvider.GetService<IOptionsManager>();
|
||||||
|
var optionsValue = configurationType
|
||||||
|
.GetMethod(nameof(IOptionsManager.GetOptions))
|
||||||
|
.MakeGenericMethod(type.GetGenericArguments())
|
||||||
|
.Invoke(configurationManager, Array.Empty<object>());
|
||||||
|
|
||||||
|
return Activator.CreateInstance(typedOptionsType, optionsValue);
|
||||||
|
}
|
||||||
|
}
|
||||||
+32
@@ -0,0 +1,32 @@
|
|||||||
|
using Slim.Resolvers;
|
||||||
|
using System.Extensions.Configuration;
|
||||||
|
|
||||||
|
namespace System.Configuration;
|
||||||
|
|
||||||
|
public sealed class UpdateableOptionsResolver : IDependencyResolver
|
||||||
|
{
|
||||||
|
private static readonly Type optionsType = typeof(UpdateableOptionsWrapper<>);
|
||||||
|
private static readonly Type configurationType = typeof(IOptionsManager);
|
||||||
|
|
||||||
|
public bool CanResolve(Type type)
|
||||||
|
{
|
||||||
|
if (type.IsGenericType && type.GetGenericTypeDefinition() == typeof(IUpdateableOptions<>))
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public object Resolve(Slim.IServiceProvider serviceProvider, Type type)
|
||||||
|
{
|
||||||
|
var typedOptionsType = optionsType.MakeGenericType(type.GetGenericArguments());
|
||||||
|
var configurationManager = serviceProvider.GetService<IOptionsManager>();
|
||||||
|
var optionsValue = configurationType
|
||||||
|
.GetMethod(nameof(IOptionsManager.GetOptions))
|
||||||
|
.MakeGenericMethod(type.GetGenericArguments())
|
||||||
|
.Invoke(configurationManager, Array.Empty<object>());
|
||||||
|
|
||||||
|
return Activator.CreateInstance(typedOptionsType, configurationManager, optionsValue);
|
||||||
|
}
|
||||||
|
}
|
||||||
+22
@@ -0,0 +1,22 @@
|
|||||||
|
using System.Configuration;
|
||||||
|
|
||||||
|
namespace System.Extensions.Configuration;
|
||||||
|
|
||||||
|
public sealed class UpdateableOptionsWrapper<T> : IUpdateableOptions<T>
|
||||||
|
where T : class
|
||||||
|
{
|
||||||
|
private readonly IOptionsManager configurationManager;
|
||||||
|
|
||||||
|
public T Value { get; }
|
||||||
|
|
||||||
|
public UpdateableOptionsWrapper(IOptionsManager configurationManager, T options)
|
||||||
|
{
|
||||||
|
this.configurationManager = configurationManager.ThrowIfNull(nameof(configurationManager));
|
||||||
|
this.Value = options;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void UpdateOption()
|
||||||
|
{
|
||||||
|
this.configurationManager.UpdateOptions(this.Value);
|
||||||
|
}
|
||||||
|
}
|
||||||
-34
@@ -1,34 +0,0 @@
|
|||||||
using Microsoft.Extensions.DependencyInjection;
|
|
||||||
using System.Net.Http;
|
|
||||||
using System.Net.WebSockets;
|
|
||||||
|
|
||||||
namespace System.Extensions;
|
|
||||||
|
|
||||||
public static class ServiceCollectionExtensions
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Register a scoped <see cref="IHttpClient{TScope}"/> to be used by the DI engine.
|
|
||||||
/// </summary>
|
|
||||||
/// <typeparam name="T">Type of the scoped <see cref="IHttpClient{TScope}"/>.</typeparam>
|
|
||||||
/// <param name="services"><see cref="IServiceCollection"/>.</param>
|
|
||||||
/// <returns><see cref="HttpClientBuilder{T}"/> to build the http client.</returns>
|
|
||||||
public static HttpClientBuilder<T> RegisterHttpClient<T>(this IServiceCollection services)
|
|
||||||
{
|
|
||||||
services.ThrowIfNull(nameof(services));
|
|
||||||
|
|
||||||
return new HttpClientBuilder<T>(services);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Register a scoped <see cref="IClientWebSocket{TScope}"/> to be used by the DI engine.
|
|
||||||
/// </summary>
|
|
||||||
/// <typeparam name="T">Type of the scoped <see cref="IClientWebSocket{TScope}"/>.</typeparam>
|
|
||||||
/// <param name="services"><see cref="IServiceCollection"/>.</param>
|
|
||||||
/// <returns><see cref="ClientWebSocketBuilder{T}"/> to build the websocket client.</returns>
|
|
||||||
public static ClientWebSocketBuilder<T> RegisterClientWebSocket<T>(this IServiceCollection services)
|
|
||||||
{
|
|
||||||
services.ThrowIfNull(nameof(services));
|
|
||||||
|
|
||||||
return new ClientWebSocketBuilder<T>(services);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
+189
@@ -0,0 +1,189 @@
|
|||||||
|
using Microsoft.Extensions.Logging;
|
||||||
|
using Microsoft.Extensions.Options;
|
||||||
|
using Slim;
|
||||||
|
using System.Configuration;
|
||||||
|
using System.DependencyInjection.Http;
|
||||||
|
using System.Http;
|
||||||
|
using System.Logging;
|
||||||
|
using System.Net.Http;
|
||||||
|
|
||||||
|
namespace System.Extensions;
|
||||||
|
|
||||||
|
public static class ServiceManagerExtensions
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Registers a <see cref="IOptionsManager"/> with the default <see cref="DefaultOptionsManager"/>.
|
||||||
|
/// This call also registers the resolver that resolves <see cref="IUpdateableOptions{T}"/> and <see cref="IOptions{T}"/>.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="serviceManager"><see cref="IServiceManager"/>.</param>
|
||||||
|
/// <returns>Provided <see cref="IServiceManager"/>.</returns>
|
||||||
|
public static IServiceProducer RegisterOptionsManager(this IServiceProducer serviceProducer)
|
||||||
|
{
|
||||||
|
serviceProducer.ThrowIfNull(nameof(serviceProducer));
|
||||||
|
|
||||||
|
serviceProducer.RegisterSingleton<IOptionsManager, DefaultOptionsManager>();
|
||||||
|
serviceProducer.RegisterOptionsResolver();
|
||||||
|
|
||||||
|
return serviceProducer;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Registers a <see cref="IOptionsManager"/>.
|
||||||
|
/// This call also registers the resolver that resolves <see cref="IUpdateableOptions{T}"/> and <see cref="IOptions{T}"/>.
|
||||||
|
/// </summary>
|
||||||
|
/// <typeparam name="T">Implementation of <see cref="IOptionsManager"/>.</typeparam>
|
||||||
|
/// <param name="serviceManager"><see cref="IServiceManager"/>.</param>
|
||||||
|
/// <returns>Provided <see cref="IServiceManager"/>.</returns>
|
||||||
|
public static IServiceProducer RegisterOptionsManager<T>(this IServiceProducer serviceProducer)
|
||||||
|
where T : IOptionsManager
|
||||||
|
{
|
||||||
|
serviceProducer.ThrowIfNull(nameof(serviceProducer));
|
||||||
|
|
||||||
|
serviceProducer.RegisterSingleton<IOptionsManager, T>();
|
||||||
|
serviceProducer.RegisterOptionsResolver();
|
||||||
|
|
||||||
|
return serviceProducer;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Registers resolvers for <see cref="IOptions{TOptions}"/> and <see cref="IUpdateableOptions{T}"/>.
|
||||||
|
/// Depends on a <see cref="IOptionsManager"/> in order to properly resolve options.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="serviceManager"><see cref="IServiceManager"/>.</param>
|
||||||
|
/// <returns><see cref="IServiceManager"/>.</returns>
|
||||||
|
public static IServiceProducer RegisterOptionsResolver(this IServiceProducer serviceProducer)
|
||||||
|
{
|
||||||
|
serviceProducer.ThrowIfNull(nameof(serviceProducer));
|
||||||
|
|
||||||
|
serviceProducer.RegisterResolver(new OptionsResolver());
|
||||||
|
serviceProducer.RegisterResolver(new UpdateableOptionsResolver());
|
||||||
|
serviceProducer.RegisterResolver(new LiveOptionsResolver());
|
||||||
|
serviceProducer.RegisterResolver(new LiveUpdateableOptionsResolver());
|
||||||
|
|
||||||
|
return serviceProducer;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Registers a <see cref="ILogsWriter"/> with the default <see cref="CVLoggerProvider"/>.
|
||||||
|
/// </summary>
|
||||||
|
/// <typeparam name="TLogsWriter">Implementation of <see cref="ILogsWriter"/>.</typeparam>
|
||||||
|
/// <param name="serviceManager"><see cref="IServiceProducer"/>.</param>
|
||||||
|
/// <returns>Provided <see cref="IServiceProducer"/>.</returns>
|
||||||
|
public static IServiceProducer RegisterLogWriter<TLogsWriter>(this IServiceProducer serviceManager)
|
||||||
|
where TLogsWriter : ILogsWriter
|
||||||
|
{
|
||||||
|
serviceManager.ThrowIfNull(nameof(serviceManager));
|
||||||
|
|
||||||
|
serviceManager.RegisterSingleton<ILogsWriter, TLogsWriter>();
|
||||||
|
serviceManager.RegisterScoped<ILoggerFactory, LoggerFactory>(sp =>
|
||||||
|
{
|
||||||
|
var factory = new LoggerFactory();
|
||||||
|
factory.AddProvider(new CVLoggerProvider(sp.GetService<ILogsWriter>()));
|
||||||
|
return factory;
|
||||||
|
});
|
||||||
|
|
||||||
|
return serviceManager;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Registers a <see cref="ILogsWriter"/> with the default <see cref="CVLoggerProvider"/>.
|
||||||
|
/// </summary>
|
||||||
|
/// <typeparam name="TILogsWriter">Interface of <see cref="ILogsWriter"/>.</typeparam>
|
||||||
|
/// <typeparam name="TLogsWriter">Implementation of <see cref="ILogsWriter"/>.</typeparam>
|
||||||
|
/// <param name="serviceManager"><see cref="IServiceProducer"/>.</param>
|
||||||
|
/// <returns>Provided <see cref="IServiceProducer"/>.</returns>
|
||||||
|
public static IServiceProducer RegisterLogWriter<TILogsWriter, TLogsWriter>(this IServiceProducer serviceManager)
|
||||||
|
where TLogsWriter : TILogsWriter
|
||||||
|
where TILogsWriter : class, ILogsWriter
|
||||||
|
{
|
||||||
|
serviceManager.ThrowIfNull(nameof(serviceManager));
|
||||||
|
|
||||||
|
serviceManager.RegisterSingleton<TILogsWriter, TLogsWriter>();
|
||||||
|
serviceManager.RegisterScoped<ILoggerFactory, LoggerFactory>(sp =>
|
||||||
|
{
|
||||||
|
var factory = new LoggerFactory();
|
||||||
|
factory.AddProvider(new CVLoggerProvider(sp.GetService<TILogsWriter>()));
|
||||||
|
return factory;
|
||||||
|
});
|
||||||
|
|
||||||
|
return serviceManager;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Register a <see cref="ILoggerFactory"/> with a <see cref="CVLoggerProvider"/>.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="serviceProducer"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
public static IServiceProducer RegisterCVLoggerFactory(this IServiceProducer serviceProducer)
|
||||||
|
{
|
||||||
|
serviceProducer.ThrowIfNull(nameof(serviceProducer));
|
||||||
|
|
||||||
|
serviceProducer.RegisterScoped<ILoggerFactory, LoggerFactory>(sp =>
|
||||||
|
{
|
||||||
|
LoggerFactory loggerFactory = new();
|
||||||
|
loggerFactory.AddProvider(new CVLoggerProvider(sp.GetService<ILogsWriter>()));
|
||||||
|
return loggerFactory;
|
||||||
|
});
|
||||||
|
|
||||||
|
return serviceProducer;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Registers a <see cref="ILoggerFactory"/>.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="serviceProducer"><see cref="IServiceProducer"/>.</param>
|
||||||
|
/// <param name="loggerFactory">Factory that produces a <see cref="ILoggerFactory"/>.</param>
|
||||||
|
/// <returns></returns>
|
||||||
|
public static IServiceProducer RegisterLoggerFactory(this IServiceProducer serviceProducer, Func<Slim.IServiceProvider, ILoggerFactory> loggerFactory)
|
||||||
|
{
|
||||||
|
serviceProducer.ThrowIfNull(nameof(serviceProducer));
|
||||||
|
|
||||||
|
serviceProducer.RegisterSingleton<ILoggerFactory, ILoggerFactory>(loggerFactory);
|
||||||
|
return serviceProducer;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Registers a <see cref="ILoggerFactory"/> with a <see cref="ILogsWriter"/> that writes to <see cref="Diagnostics.Debug"/>.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="serviceProducer"><see cref="IServiceProducer"/>.</param>
|
||||||
|
/// <returns></returns>
|
||||||
|
public static IServiceProducer RegisterDebugLoggerFactory(this IServiceProducer serviceProducer)
|
||||||
|
{
|
||||||
|
serviceProducer.ThrowIfNull(nameof(serviceProducer));
|
||||||
|
|
||||||
|
serviceProducer.RegisterLogWriter<ILogsWriter, DebugLogsWriter>();
|
||||||
|
return serviceProducer;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Register a scoped <see cref="IHttpClient{TScope}"/> to be used by the DI engine.
|
||||||
|
/// </summary>
|
||||||
|
/// <typeparam name="T">Type of the scoped <see cref="IHttpClient{TScope}"/>.</typeparam>
|
||||||
|
/// <param name="serviceProducer"><see cref="IServiceProducer"/>.</param>
|
||||||
|
/// <returns><see cref="HttpClientBuilder{T}"/> to build the http client.</returns>
|
||||||
|
public static HttpClientBuilder<T> RegisterHttpClient<T>(this IServiceProducer serviceProducer)
|
||||||
|
{
|
||||||
|
serviceProducer.ThrowIfNull(nameof(serviceProducer));
|
||||||
|
|
||||||
|
return new HttpClientBuilder<T>(serviceProducer);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Obsolete($"{nameof(RegisterHttpFactory)} is obsolete. Please use {nameof(RegisterHttpClient)} for each service that requires an instance of {nameof(IHttpClient<object>)}.")]
|
||||||
|
public static IServiceProducer RegisterHttpFactory(this IServiceProducer serviceProducer)
|
||||||
|
{
|
||||||
|
serviceProducer.ThrowIfNull(nameof(serviceProducer));
|
||||||
|
|
||||||
|
serviceProducer.RegisterResolver(new HttpClientResolver());
|
||||||
|
return serviceProducer;
|
||||||
|
}
|
||||||
|
[Obsolete($"{nameof(RegisterHttpFactory)} is obsolete. Please use {nameof(RegisterHttpClient)} for each service that requires an instance of {nameof(IHttpClient<object>)}.")]
|
||||||
|
public static IServiceProducer RegisterHttpFactory(this IServiceProducer serviceProducer, Func<Slim.IServiceProvider, Type, HttpMessageHandler> handlerFactory)
|
||||||
|
{
|
||||||
|
serviceProducer.ThrowIfNull(nameof(serviceProducer));
|
||||||
|
|
||||||
|
serviceProducer.RegisterResolver(
|
||||||
|
new HttpClientResolver()
|
||||||
|
.WithHttpMessageHandlerFactory(handlerFactory));
|
||||||
|
return serviceProducer;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,25 +1,26 @@
|
|||||||
using Microsoft.Extensions.DependencyInjection;
|
using Slim;
|
||||||
using System.Extensions;
|
using System.Extensions;
|
||||||
|
using System.Net.Http;
|
||||||
using System.Net.Http.Headers;
|
using System.Net.Http.Headers;
|
||||||
|
|
||||||
namespace System.Net.Http;
|
namespace System.DependencyInjection.Http;
|
||||||
|
|
||||||
public sealed class HttpClientBuilder<T>
|
public sealed class HttpClientBuilder<T>
|
||||||
{
|
{
|
||||||
private readonly IServiceCollection services;
|
private readonly IServiceProducer serviceProducer;
|
||||||
|
|
||||||
private Uri? baseAddress;
|
private Uri baseAddress;
|
||||||
private Func<IServiceProvider, HttpMessageHandler>? httpMessageHandlerFactory;
|
private Func<IServiceProvider, HttpMessageHandler> httpMessageHandlerFactory;
|
||||||
private Action<HttpRequestHeaders>? defaultRequestHeadersSetup;
|
|
||||||
private bool disposeMessageHandler;
|
private bool disposeMessageHandler;
|
||||||
|
private Action<HttpRequestHeaders> defaultRequestHeadersSetup;
|
||||||
private long maxResponseBufferSize = 2147483647L; //2GB default HttpClient value [https://docs.microsoft.com/en-us/dotnet/api/system.net.http.httpclient.maxresponsecontentbuffersize?view=net-6.0]
|
private long maxResponseBufferSize = 2147483647L; //2GB default HttpClient value [https://docs.microsoft.com/en-us/dotnet/api/system.net.http.httpclient.maxresponsecontentbuffersize?view=net-6.0]
|
||||||
private TimeSpan timeout = TimeSpan.FromSeconds(100); //100 seconds default HttpClient value [https://docs.microsoft.com/en-us/dotnet/api/system.net.http.httpclient.timeout?view=net-6.0]
|
private TimeSpan timeout = TimeSpan.FromSeconds(100); //100 seconds default HttpClient value [https://docs.microsoft.com/en-us/dotnet/api/system.net.http.httpclient.timeout?view=net-6.0]
|
||||||
|
|
||||||
internal HttpClientBuilder(IServiceCollection services)
|
internal HttpClientBuilder(IServiceProducer serviceProducer)
|
||||||
{
|
{
|
||||||
services.ThrowIfNull(nameof(services));
|
serviceProducer.ThrowIfNull(nameof(serviceProducer));
|
||||||
|
|
||||||
this.services = services;
|
this.serviceProducer = serviceProducer;
|
||||||
}
|
}
|
||||||
|
|
||||||
public HttpClientBuilder<T> WithMessageHandler(Func<IServiceProvider, HttpMessageHandler> httpMessageHandlerFactory)
|
public HttpClientBuilder<T> WithMessageHandler(Func<IServiceProvider, HttpMessageHandler> httpMessageHandlerFactory)
|
||||||
@@ -64,25 +65,21 @@ public sealed class HttpClientBuilder<T>
|
|||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
public IServiceCollection Build()
|
public IServiceProducer Build()
|
||||||
{
|
{
|
||||||
this.services.AddScoped<IHttpClient<T>>(sp =>
|
this.serviceProducer.RegisterScoped<IHttpClient<T>>(sp =>
|
||||||
{
|
{
|
||||||
var client = this.httpMessageHandlerFactory is not null ?
|
var client = this.httpMessageHandlerFactory is not null ?
|
||||||
new HttpClient<T>(this.httpMessageHandlerFactory(sp), this.disposeMessageHandler) :
|
new HttpClient<T>(this.httpMessageHandlerFactory(sp), this.disposeMessageHandler) :
|
||||||
new HttpClient<T>(true);
|
new HttpClient<T>();
|
||||||
|
|
||||||
if (this.baseAddress is not null)
|
|
||||||
{
|
|
||||||
client.BaseAddress = this.baseAddress;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
client.BaseAddress = this.baseAddress;
|
||||||
this.defaultRequestHeadersSetup?.Invoke(client.DefaultRequestHeaders);
|
this.defaultRequestHeadersSetup?.Invoke(client.DefaultRequestHeaders);
|
||||||
client.MaxResponseContentBufferSize = this.maxResponseBufferSize;
|
client.MaxResponseContentBufferSize = this.maxResponseBufferSize;
|
||||||
client.Timeout = this.timeout;
|
client.Timeout = this.timeout;
|
||||||
return client;
|
return client;
|
||||||
});
|
});
|
||||||
|
|
||||||
return this.services;
|
return this.serviceProducer;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,47 @@
|
|||||||
|
using Slim.Resolvers;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Net.Http;
|
||||||
|
|
||||||
|
namespace System.Http;
|
||||||
|
|
||||||
|
[Obsolete($"Please use {nameof(Extensions.ServiceManagerExtensions.RegisterHttpClient)} for each use case of {nameof(IHttpClient<object>)}")]
|
||||||
|
public sealed class HttpClientResolver : IDependencyResolver
|
||||||
|
{
|
||||||
|
private static readonly Type clientType = typeof(HttpClient<>);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Factory method. <see cref="Type"/> parameter of the factory is the scope of <see cref="IHttpClient{TScope}"/>.
|
||||||
|
/// </summary>
|
||||||
|
public Func<Slim.IServiceProvider, Type, HttpMessageHandler> HttpMessageHandlerFactory { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Sets the <see cref="HttpMessageHandlerFactory"/>.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="factory">Factory method. <see cref="Type"/> parameter of the factory is the scope of <see cref="IHttpClient{TScope}"/>.</param>
|
||||||
|
/// <returns></returns>
|
||||||
|
public HttpClientResolver WithHttpMessageHandlerFactory(Func<Slim.IServiceProvider, Type, HttpMessageHandler> factory)
|
||||||
|
{
|
||||||
|
this.HttpMessageHandlerFactory = factory;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool CanResolve(Type type)
|
||||||
|
{
|
||||||
|
if (type.IsGenericType && type.GetGenericTypeDefinition() == typeof(IHttpClient<>))
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public object Resolve(Slim.IServiceProvider serviceProvider, Type type)
|
||||||
|
{
|
||||||
|
var typedClientType = clientType.MakeGenericType(type.GetGenericArguments());
|
||||||
|
var handler = this.HttpMessageHandlerFactory?.Invoke(serviceProvider, type.GetGenericArguments().First());
|
||||||
|
var httpClient = handler is null ?
|
||||||
|
Activator.CreateInstance(typedClientType) :
|
||||||
|
Activator.CreateInstance(typedClientType, new object[] { handler });
|
||||||
|
return httpClient;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,43 @@
|
|||||||
|
using Microsoft.Extensions.Logging;
|
||||||
|
using System.Extensions;
|
||||||
|
|
||||||
|
namespace System.Logging;
|
||||||
|
|
||||||
|
public sealed class CVLogger : ILogger
|
||||||
|
{
|
||||||
|
private readonly string category;
|
||||||
|
private readonly ICVLoggerProvider cvLoggerProvider;
|
||||||
|
|
||||||
|
public CVLogger(string category, ICVLoggerProvider cvLoggerProvider)
|
||||||
|
{
|
||||||
|
this.category = category;
|
||||||
|
this.cvLoggerProvider = cvLoggerProvider.ThrowIfNull(nameof(cvLoggerProvider));
|
||||||
|
}
|
||||||
|
|
||||||
|
public IDisposable BeginScope<TState>(TState state)
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool IsEnabled(LogLevel logLevel)
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Log<TState>(LogLevel logLevel, EventId eventId, TState state, Exception exception, Func<TState, Exception, string> formatter)
|
||||||
|
{
|
||||||
|
var message = formatter(state, exception);
|
||||||
|
|
||||||
|
var log = new Log
|
||||||
|
{
|
||||||
|
Exception = exception,
|
||||||
|
LogLevel = logLevel,
|
||||||
|
EventId = eventId.Name,
|
||||||
|
Message = message,
|
||||||
|
Category = category,
|
||||||
|
LogTime = DateTime.Now
|
||||||
|
};
|
||||||
|
|
||||||
|
this.cvLoggerProvider.LogEntry(log);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,38 @@
|
|||||||
|
using Microsoft.CorrelationVector;
|
||||||
|
using Microsoft.Extensions.Logging;
|
||||||
|
using System.Extensions;
|
||||||
|
|
||||||
|
namespace System.Logging;
|
||||||
|
|
||||||
|
public sealed class CVLoggerProvider : ICVLoggerProvider
|
||||||
|
{
|
||||||
|
private readonly ILogsWriter logsManager;
|
||||||
|
private readonly CorrelationVector correlationVector;
|
||||||
|
|
||||||
|
public CVLoggerProvider(ILogsWriter logsWriter)
|
||||||
|
{
|
||||||
|
this.logsManager = logsWriter.ThrowIfNull(nameof(logsWriter));
|
||||||
|
this.correlationVector = new CorrelationVector();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void LogEntry(Log log)
|
||||||
|
{
|
||||||
|
if (this.correlationVector is not null)
|
||||||
|
{
|
||||||
|
log.CorrelationVector = this.correlationVector.Value.ToString();
|
||||||
|
this.correlationVector.Increment();
|
||||||
|
}
|
||||||
|
|
||||||
|
this.logsManager.WriteLog(log);
|
||||||
|
}
|
||||||
|
|
||||||
|
public ILogger CreateLogger(string categoryName)
|
||||||
|
{
|
||||||
|
return new CVLogger(categoryName, this);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
throw new System.NotImplementedException();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
using System.Diagnostics;
|
||||||
|
|
||||||
|
namespace System.Logging;
|
||||||
|
|
||||||
|
public sealed class DebugLogsWriter : ILogsWriter
|
||||||
|
{
|
||||||
|
public void WriteLog(Log log)
|
||||||
|
{
|
||||||
|
Debug.WriteLine($"{log.LogTime} - {log.Category} - {log.EventId} - {log.CorrelationVector} - {log.LogLevel} - {log.Message}{Environment.NewLine}{log.Exception}");
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
using Microsoft.Extensions.Logging;
|
||||||
|
|
||||||
|
namespace System.Logging;
|
||||||
|
|
||||||
|
public interface ICVLoggerProvider : ILoggerProvider
|
||||||
|
{
|
||||||
|
void LogEntry(Log log);
|
||||||
|
}
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
namespace System.Logging;
|
||||||
|
|
||||||
|
public interface ILogsWriter
|
||||||
|
{
|
||||||
|
void WriteLog(Log log);
|
||||||
|
}
|
||||||
@@ -0,0 +1,49 @@
|
|||||||
|
using Microsoft.Extensions.Logging;
|
||||||
|
using Slim.Resolvers;
|
||||||
|
using System.Linq;
|
||||||
|
|
||||||
|
namespace System.Logging;
|
||||||
|
|
||||||
|
public sealed class LoggerResolver : IDependencyResolver
|
||||||
|
{
|
||||||
|
public bool CanResolve(Type type)
|
||||||
|
{
|
||||||
|
if (type.IsGenericType && type.GetGenericTypeDefinition() == typeof(ILogger<>) ||
|
||||||
|
type == typeof(ILogger))
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public object Resolve(Slim.IServiceProvider serviceProvider, Type type)
|
||||||
|
{
|
||||||
|
if (type.IsGenericType && type.GetGenericTypeDefinition() == typeof(ILogger<>))
|
||||||
|
{
|
||||||
|
return ResolveScopedLogger(serviceProvider, type);
|
||||||
|
}
|
||||||
|
else if (type == typeof(ILogger))
|
||||||
|
{
|
||||||
|
return ResolveLogger(serviceProvider);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
throw new InvalidOperationException($"{nameof(LoggerResolver)} cannot resolve an object of type {type.Name}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static object ResolveScopedLogger(Slim.IServiceProvider serviceProvider, Type type)
|
||||||
|
{
|
||||||
|
var loggerFactory = serviceProvider.GetService<ILoggerFactory>();
|
||||||
|
var categoryTypes = type.GetGenericArguments();
|
||||||
|
var createLoggerMethod = typeof(LoggerFactoryExtensions).GetMethods().Where(m => m.IsGenericMethodDefinition && m.Name == nameof(LoggerFactoryExtensions.CreateLogger)).First();
|
||||||
|
return createLoggerMethod.MakeGenericMethod(categoryTypes.First()).Invoke(null, new object[] { loggerFactory });
|
||||||
|
}
|
||||||
|
|
||||||
|
private static object ResolveLogger(Slim.IServiceProvider serviceProvider)
|
||||||
|
{
|
||||||
|
var loggerFactory = serviceProvider.GetService<ILoggerFactory>();
|
||||||
|
return loggerFactory.CreateLogger(string.Empty);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
using Microsoft.Extensions.Logging;
|
||||||
|
|
||||||
|
namespace System.Logging;
|
||||||
|
|
||||||
|
public sealed record Log
|
||||||
|
{
|
||||||
|
public Exception Exception { get; set; }
|
||||||
|
public DateTime LogTime { get; set; }
|
||||||
|
public string Category { get; set; }
|
||||||
|
public string EventId { get; set; }
|
||||||
|
public LogLevel LogLevel { get; set; }
|
||||||
|
public string Message { get; set; }
|
||||||
|
public string CorrelationVector { get; set; }
|
||||||
|
}
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
using System.Net.Http;
|
||||||
|
|
||||||
|
namespace System.DependencyInjection.Models;
|
||||||
|
|
||||||
|
internal sealed class TypedHttpClientFactory<T>
|
||||||
|
{
|
||||||
|
public Func<Slim.IServiceProvider, HttpMessageHandler> Factory { get; set; }
|
||||||
|
}
|
||||||
+4
-4
@@ -6,11 +6,10 @@
|
|||||||
<PackageLicenseFile>LICENSE</PackageLicenseFile>
|
<PackageLicenseFile>LICENSE</PackageLicenseFile>
|
||||||
<PackageRequireLicenseAcceptance>true</PackageRequireLicenseAcceptance>
|
<PackageRequireLicenseAcceptance>true</PackageRequireLicenseAcceptance>
|
||||||
<LangVersion>latest</LangVersion>
|
<LangVersion>latest</LangVersion>
|
||||||
<Version>1.8</Version>
|
<Version>1.2.3</Version>
|
||||||
<Authors>Alexandru Macocian</Authors>
|
<Authors>Alexandru Macocian</Authors>
|
||||||
<RepositoryUrl>https://github.com/AlexMacocian/SystemExtensions</RepositoryUrl>
|
<RepositoryUrl>https://github.com/AlexMacocian/SystemExtensions</RepositoryUrl>
|
||||||
<Description>Extensions for the Slim Dependency Injection engine</Description>
|
<Description>Extensions for the Slim Dependency Injection engine</Description>
|
||||||
<Nullable>enable</Nullable>
|
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
@@ -22,8 +21,9 @@
|
|||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="Microsoft.CorrelationVector" Version="1.0.42" />
|
<PackageReference Include="Microsoft.CorrelationVector" Version="1.0.42" />
|
||||||
<PackageReference Include="Microsoft.Extensions.Logging" Version="10.0.1" />
|
<PackageReference Include="Microsoft.Extensions.Logging" Version="6.0.0" />
|
||||||
<PackageReference Include="SystemExtensions.NetStandard" Version="1.8.0" />
|
<PackageReference Include="Slim" Version="1.9.2" />
|
||||||
|
<PackageReference Include="SystemExtensions.NetStandard" Version="1.5.0" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
</Project>
|
</Project>
|
||||||
|
|||||||
@@ -1,58 +0,0 @@
|
|||||||
using Microsoft.Extensions.DependencyInjection;
|
|
||||||
using Microsoft.Extensions.Logging;
|
|
||||||
using System.Extensions;
|
|
||||||
using System.Net.WebSockets;
|
|
||||||
|
|
||||||
namespace System.Net.Http;
|
|
||||||
|
|
||||||
public sealed class ClientWebSocketBuilder<T>
|
|
||||||
{
|
|
||||||
private readonly IServiceCollection services;
|
|
||||||
|
|
||||||
private ClientWebSocket? innerWebSocket;
|
|
||||||
private Func<IServiceProvider, ClientWebSocket>? innerWebSocketFactory;
|
|
||||||
|
|
||||||
internal ClientWebSocketBuilder(IServiceCollection services)
|
|
||||||
{
|
|
||||||
services.ThrowIfNull(nameof(services));
|
|
||||||
this.services = services;
|
|
||||||
}
|
|
||||||
|
|
||||||
public ClientWebSocketBuilder<T> WithInnerWebSocket(ClientWebSocket innerWebSocket)
|
|
||||||
{
|
|
||||||
this.innerWebSocket = innerWebSocket.ThrowIfNull(nameof(innerWebSocket));
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public ClientWebSocketBuilder<T> WithInnerWebSocketFactory(Func<IServiceProvider, ClientWebSocket> innerWebSocketFactory)
|
|
||||||
{
|
|
||||||
this.innerWebSocketFactory = innerWebSocketFactory.ThrowIfNull(nameof(innerWebSocketFactory));
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public IServiceCollection Build()
|
|
||||||
{
|
|
||||||
this.services.AddScoped<IClientWebSocket<T>>(sp =>
|
|
||||||
{
|
|
||||||
var logger = sp.GetService<ILogger<T>>();
|
|
||||||
if (logger is null)
|
|
||||||
{
|
|
||||||
return new ClientWebSocket<T>();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this.innerWebSocketFactory is not null)
|
|
||||||
{
|
|
||||||
return new ClientWebSocket<T>(this.innerWebSocketFactory(sp), logger);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this.innerWebSocket is not null)
|
|
||||||
{
|
|
||||||
return new ClientWebSocket<T>(this.innerWebSocket, logger);
|
|
||||||
}
|
|
||||||
|
|
||||||
return new ClientWebSocket<T>(logger);
|
|
||||||
});
|
|
||||||
|
|
||||||
return this.services;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,142 +0,0 @@
|
|||||||
using Microsoft.CodeAnalysis;
|
|
||||||
using Sybil;
|
|
||||||
using System.Text;
|
|
||||||
using System.Linq;
|
|
||||||
|
|
||||||
namespace System.Extensions;
|
|
||||||
#nullable enable
|
|
||||||
[Generator(LanguageNames.CSharp)]
|
|
||||||
public class BlittableStringGenerator : IIncrementalGenerator
|
|
||||||
{
|
|
||||||
private const string UsingSystem = "System";
|
|
||||||
private const string UsingSystemRuntimeInterop = "System.Runtime.InteropServices";
|
|
||||||
private const string AttributeNamespace = "System.Extensions";
|
|
||||||
private const string AttributeName = "GenerateBlittableStringAttribute";
|
|
||||||
private const string AttributeShortName = "GenerateBlittableString";
|
|
||||||
private const string String = "String";
|
|
||||||
private const string Public = "public";
|
|
||||||
private const string Required = "required";
|
|
||||||
private const string Size = "Size";
|
|
||||||
private const string Int = "int";
|
|
||||||
|
|
||||||
public void Initialize(IncrementalGeneratorInitializationContext context)
|
|
||||||
{
|
|
||||||
context.RegisterPostInitializationOutput(context =>
|
|
||||||
{
|
|
||||||
var compilationUnitBuilder = SyntaxBuilder.CreateCompilationUnit()
|
|
||||||
.WithNamespace(
|
|
||||||
SyntaxBuilder.CreateNamespace(AttributeNamespace)
|
|
||||||
.WithClass(SyntaxBuilder.CreateClass(AttributeName)
|
|
||||||
.WithModifier(Public)
|
|
||||||
.WithConstructor(SyntaxBuilder.CreateConstructor(AttributeName)
|
|
||||||
.WithModifier(Public))
|
|
||||||
.WithAttribute(SyntaxBuilder.CreateAttribute("AttributeUsage")
|
|
||||||
.WithArgument(AttributeTargets.Assembly)
|
|
||||||
.WithArgument("Inherited", false)
|
|
||||||
.WithArgument("AllowMultiple", true))
|
|
||||||
.WithProperty(SyntaxBuilder.CreateProperty(Int, Size)
|
|
||||||
.WithModifier(Public)
|
|
||||||
.WithModifier(Required)
|
|
||||||
.WithAccessor(SyntaxBuilder.CreateGetter())
|
|
||||||
.WithAccessor(SyntaxBuilder.CreateSetter()))
|
|
||||||
.WithBaseClass(nameof(Attribute))));
|
|
||||||
var compilationUnitSyntax = compilationUnitBuilder.Build();
|
|
||||||
var source = compilationUnitSyntax.ToFullString();
|
|
||||||
context.AddSource($"{AttributeName}.g", source);
|
|
||||||
});
|
|
||||||
|
|
||||||
context.RegisterSourceOutput(context.CompilationProvider, (sourceProductionContext, compilation) =>
|
|
||||||
{
|
|
||||||
var assemblyAttributes =
|
|
||||||
compilation.Assembly.GetAttributes().Where(a => a.AttributeClass != null &&
|
|
||||||
(a.AttributeClass.Name == AttributeName || a.AttributeClass.Name == AttributeShortName));
|
|
||||||
|
|
||||||
foreach (var attr in assemblyAttributes)
|
|
||||||
{
|
|
||||||
// Extract the Size parameter from the named arguments.
|
|
||||||
int? fixedSize = default;
|
|
||||||
foreach (var arg in attr.NamedArguments)
|
|
||||||
{
|
|
||||||
if (arg.Key == Size && arg.Value.Value is int intValue)
|
|
||||||
{
|
|
||||||
fixedSize = intValue;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (fixedSize is null)
|
|
||||||
{
|
|
||||||
// As Size is required, skip if not provided.
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
Execute(sourceProductionContext, (int)fixedSize);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
private static void Execute(SourceProductionContext sourceProductionContext, int size)
|
|
||||||
{
|
|
||||||
var structName = $"{String}{size}";
|
|
||||||
var builder = new StringBuilder();
|
|
||||||
builder.AppendLine($"using {UsingSystem};");
|
|
||||||
builder.AppendLine($"using {UsingSystemRuntimeInterop};");
|
|
||||||
builder.AppendLine();
|
|
||||||
builder.AppendLine($"namespace {AttributeNamespace};");
|
|
||||||
builder.AppendLine();
|
|
||||||
builder.AppendLine("[StructLayout(LayoutKind.Sequential, Pack = 1)]");
|
|
||||||
builder.AppendLine($"public unsafe partial struct {structName}");
|
|
||||||
builder.AppendLine("{");
|
|
||||||
builder.AppendLine($" public const int Size = {size};");
|
|
||||||
builder.AppendLine();
|
|
||||||
builder.AppendLine(" public fixed char Value[Size];");
|
|
||||||
builder.AppendLine();
|
|
||||||
builder.AppendLine(" public Span<char> AsSpan()");
|
|
||||||
builder.AppendLine(" {");
|
|
||||||
builder.AppendLine(" fixed (char* ptr = this.Value)");
|
|
||||||
builder.AppendLine(" {");
|
|
||||||
builder.AppendLine(" return MemoryMarshal.CreateSpan(ref *ptr, Size);");
|
|
||||||
builder.AppendLine(" }");
|
|
||||||
builder.AppendLine(" }");
|
|
||||||
builder.AppendLine();
|
|
||||||
builder.AppendLine(" public readonly ReadOnlySpan<char> AsReadOnlySpan()");
|
|
||||||
builder.AppendLine(" {");
|
|
||||||
builder.AppendLine(" fixed (char* ptr = this.Value)");
|
|
||||||
builder.AppendLine(" {");
|
|
||||||
builder.AppendLine(" return new ReadOnlySpan<char>(ptr, Size);");
|
|
||||||
builder.AppendLine(" }");
|
|
||||||
builder.AppendLine(" }");
|
|
||||||
builder.AppendLine();
|
|
||||||
builder.AppendLine(" public void Set(ReadOnlySpan<char> value)");
|
|
||||||
builder.AppendLine(" {");
|
|
||||||
builder.AppendLine(" fixed (char* ptr = this.Value)");
|
|
||||||
builder.AppendLine(" {");
|
|
||||||
builder.AppendLine(" var span = MemoryMarshal.CreateSpan(ref *ptr, Size);");
|
|
||||||
builder.AppendLine(" span.Clear();");
|
|
||||||
builder.AppendLine(" value[..Math.Min(value.Length, Size - 1)].CopyTo(span);");
|
|
||||||
builder.AppendLine(" }");
|
|
||||||
builder.AppendLine(" }");
|
|
||||||
builder.AppendLine();
|
|
||||||
builder.AppendLine(" public override readonly string ToString()");
|
|
||||||
builder.AppendLine(" {");
|
|
||||||
builder.AppendLine(" var span = this.AsReadOnlySpan();");
|
|
||||||
builder.AppendLine(" var nullIndex = span.IndexOf('\\0');");
|
|
||||||
builder.AppendLine(" return nullIndex >= 0 ? new string(span[..nullIndex]) : new string(span);");
|
|
||||||
builder.AppendLine(" }");
|
|
||||||
builder.AppendLine();
|
|
||||||
builder.AppendLine($" public static implicit operator string({structName} value) => value.ToString();");
|
|
||||||
builder.AppendLine();
|
|
||||||
builder.AppendLine($" public static implicit operator {structName}(string value)");
|
|
||||||
builder.AppendLine(" {");
|
|
||||||
builder.AppendLine($" var result = default({structName});");
|
|
||||||
builder.AppendLine(" result.Set(value);");
|
|
||||||
builder.AppendLine(" return result;");
|
|
||||||
builder.AppendLine(" }");
|
|
||||||
builder.AppendLine();
|
|
||||||
builder.AppendLine($" public static implicit operator ReadOnlySpan<char>({structName} value) => value.AsReadOnlySpan();");
|
|
||||||
builder.AppendLine("}");
|
|
||||||
|
|
||||||
sourceProductionContext.AddSource($"{structName}.g.cs", builder.ToString());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
#nullable disable
|
|
||||||
@@ -1,78 +0,0 @@
|
|||||||
using Microsoft.CodeAnalysis;
|
|
||||||
using Microsoft.CodeAnalysis.Text;
|
|
||||||
|
|
||||||
namespace System.Extensions;
|
|
||||||
|
|
||||||
internal static class Diagnostics
|
|
||||||
{
|
|
||||||
public const string DependencyPropertyGenerator_NoAttributeFound = "DGD 0001";
|
|
||||||
public const string DependencyPropertyGenerator_ClassMustBeTopLevel = "DGD 1001";
|
|
||||||
public const string DependencyPropertyGenerator_ClassMustBePartial = "DGD 1002";
|
|
||||||
public const string DependencyPropertyGenerator_ClassMustBeInNamespace = "DGD 1003";
|
|
||||||
public const string DependencyPropertyGenerator_ClassMustImplementINotifyPropertyChanged = "DGD 1004";
|
|
||||||
|
|
||||||
public static Diagnostic MissingAttributeDiagnostic(string attributeName, string attributeNamespace) => Diagnostic.Create(
|
|
||||||
new DiagnosticDescriptor(
|
|
||||||
DependencyPropertyGenerator_NoAttributeFound,
|
|
||||||
$"{attributeNamespace}.{attributeName} not found",
|
|
||||||
"Could not find attribute with name {0} in namespace {1}.",
|
|
||||||
"SystemExtensions.NetStandard.Generators",
|
|
||||||
DiagnosticSeverity.Error,
|
|
||||||
true,
|
|
||||||
$"This error occurs when the attribute generated by the {nameof(NotifyPropertyChangedGenerator)} is not found",
|
|
||||||
null),
|
|
||||||
Location.None,
|
|
||||||
attributeName, attributeNamespace);
|
|
||||||
|
|
||||||
public static Diagnostic ClassNotTopLevelDiagnostic(string className, string containingSymbol, SyntaxTree syntaxTree, TextSpan textSpan) => Diagnostic.Create(
|
|
||||||
new DiagnosticDescriptor(
|
|
||||||
DependencyPropertyGenerator_ClassMustBeTopLevel,
|
|
||||||
$"{className} must be top level",
|
|
||||||
"Class {0} must be top level. It is currently declared under {1}.",
|
|
||||||
"SystemExtensions.NetStandard.Generators",
|
|
||||||
DiagnosticSeverity.Error,
|
|
||||||
true,
|
|
||||||
null,
|
|
||||||
null),
|
|
||||||
Location.Create(syntaxTree, textSpan),
|
|
||||||
className, containingSymbol);
|
|
||||||
|
|
||||||
public static Diagnostic ClassNotPartial(string className) => Diagnostic.Create(
|
|
||||||
new DiagnosticDescriptor(
|
|
||||||
DependencyPropertyGenerator_ClassMustBePartial,
|
|
||||||
$"{className} is not partial",
|
|
||||||
"Class {0} must be marked as partial in order for the generator to work",
|
|
||||||
"SystemExtensions.NetStandard.Generators",
|
|
||||||
DiagnosticSeverity.Error,
|
|
||||||
true,
|
|
||||||
null,
|
|
||||||
null),
|
|
||||||
Location.None,
|
|
||||||
className);
|
|
||||||
|
|
||||||
public static Diagnostic ClassNotInNamespace(string className) => Diagnostic.Create(
|
|
||||||
new DiagnosticDescriptor(
|
|
||||||
DependencyPropertyGenerator_ClassMustBeInNamespace,
|
|
||||||
$"{className} is not defined in a namespace",
|
|
||||||
"Class {0} must be defined inside a namespace for the generator to work",
|
|
||||||
"SystemExtensions.NetStandard.Generators",
|
|
||||||
DiagnosticSeverity.Error,
|
|
||||||
true,
|
|
||||||
null,
|
|
||||||
null),
|
|
||||||
Location.None,
|
|
||||||
className);
|
|
||||||
|
|
||||||
public static Diagnostic ClassDoesNotImplementINotifyPropertyChanged(string className) => Diagnostic.Create(
|
|
||||||
new DiagnosticDescriptor(
|
|
||||||
DependencyPropertyGenerator_ClassMustImplementINotifyPropertyChanged,
|
|
||||||
$"{className} must implement INotifyPropertyChanged",
|
|
||||||
"Class {0} must implement INotifyPropertyChanged for the generator to work",
|
|
||||||
"SystemExtensions.NetStandard.Generators",
|
|
||||||
DiagnosticSeverity.Error,
|
|
||||||
true,
|
|
||||||
null,
|
|
||||||
null),
|
|
||||||
Location.None,
|
|
||||||
className);
|
|
||||||
}
|
|
||||||
@@ -1,164 +0,0 @@
|
|||||||
using Microsoft.CodeAnalysis;
|
|
||||||
using Sybil;
|
|
||||||
using System.Text;
|
|
||||||
using System.Linq;
|
|
||||||
|
|
||||||
namespace System.Extensions;
|
|
||||||
#nullable enable
|
|
||||||
[Generator(LanguageNames.CSharp)]
|
|
||||||
public class FixedArrayGenerator : IIncrementalGenerator
|
|
||||||
{
|
|
||||||
private const string UsingSystem = "System";
|
|
||||||
private const string UsingSystemRuntimeInterop = "System.Runtime.InteropServices";
|
|
||||||
private const string AttributeNamespace = "System.Extensions";
|
|
||||||
private const string AttributeName = "GenerateFixedArrayAttribute";
|
|
||||||
private const string AttributeShortName = "GenerateFixedArray";
|
|
||||||
private const string Array = "Array";
|
|
||||||
private const string Public = "public";
|
|
||||||
private const string Struct = "struct";
|
|
||||||
private const string Required = "required";
|
|
||||||
private const string Size = "Size";
|
|
||||||
private const string Int = "int";
|
|
||||||
private const string TType = "T";
|
|
||||||
|
|
||||||
public void Initialize(IncrementalGeneratorInitializationContext context)
|
|
||||||
{
|
|
||||||
context.RegisterPostInitializationOutput(context =>
|
|
||||||
{
|
|
||||||
var compilationUnitBuilder = SyntaxBuilder.CreateCompilationUnit()
|
|
||||||
.WithNamespace(
|
|
||||||
SyntaxBuilder.CreateNamespace(AttributeNamespace)
|
|
||||||
.WithClass(SyntaxBuilder.CreateClass(AttributeName)
|
|
||||||
.WithModifier(Public)
|
|
||||||
.WithTypeParameter(SyntaxBuilder.CreateTypeParameter(TType))
|
|
||||||
.WithTypeParameterConstraint(SyntaxBuilder.CreateTypeParameterConstraint(TType)
|
|
||||||
.WithType(Struct))
|
|
||||||
.WithConstructor(SyntaxBuilder.CreateConstructor(AttributeName)
|
|
||||||
.WithModifier(Public))
|
|
||||||
.WithAttribute(SyntaxBuilder.CreateAttribute("AttributeUsage")
|
|
||||||
.WithArgument(AttributeTargets.Assembly)
|
|
||||||
.WithArgument("Inherited", false)
|
|
||||||
.WithArgument("AllowMultiple", true))
|
|
||||||
.WithProperty(SyntaxBuilder.CreateProperty(Int, Size)
|
|
||||||
.WithModifier(Public)
|
|
||||||
.WithModifier(Required)
|
|
||||||
.WithAccessor(SyntaxBuilder.CreateGetter())
|
|
||||||
.WithAccessor(SyntaxBuilder.CreateSetter()))
|
|
||||||
.WithBaseClass(nameof(Attribute))));
|
|
||||||
var compilationUnitSyntax = compilationUnitBuilder.Build();
|
|
||||||
var source = compilationUnitSyntax.ToFullString();
|
|
||||||
context.AddSource($"{AttributeName}.g", source);
|
|
||||||
});
|
|
||||||
|
|
||||||
context.RegisterSourceOutput(context.CompilationProvider, (sourceProductionContext, compilation) =>
|
|
||||||
{
|
|
||||||
var assemblyAttributes =
|
|
||||||
compilation.Assembly.GetAttributes().Where(a => a.AttributeClass != null &&
|
|
||||||
(a.AttributeClass.Name == AttributeName || a.AttributeClass.Name == AttributeShortName));
|
|
||||||
|
|
||||||
foreach (var attr in assemblyAttributes)
|
|
||||||
{
|
|
||||||
// Extract the type argument (T) from GenerateFixedArrayAttribute<T>
|
|
||||||
var namedTypeSymbol = attr.AttributeClass as INamedTypeSymbol;
|
|
||||||
if (namedTypeSymbol is null || namedTypeSymbol.TypeArguments.Length != 1)
|
|
||||||
{
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
var targetType = namedTypeSymbol.TypeArguments[0].ToDisplayString(SymbolDisplayFormat.MinimallyQualifiedFormat);
|
|
||||||
// Extract the Size parameter from the named arguments.
|
|
||||||
int? fixedSize = default;
|
|
||||||
foreach (var arg in attr.NamedArguments)
|
|
||||||
{
|
|
||||||
if (arg.Key == Size && arg.Value.Value is int intValue)
|
|
||||||
{
|
|
||||||
fixedSize = intValue;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (fixedSize is null)
|
|
||||||
{
|
|
||||||
// As Size is required, skip if not provided.
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
Execute(sourceProductionContext, targetType, (int)fixedSize);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
private static void Execute(SourceProductionContext sourceProductionContext, string targetType, int size)
|
|
||||||
{
|
|
||||||
var capitalizedType = targetType.ToUpperInvariant()[0] + targetType.Substring(1);
|
|
||||||
var structName = $"{Array}{size}{capitalizedType}";
|
|
||||||
var builder = new StringBuilder();
|
|
||||||
builder.AppendLine($"using {UsingSystem};");
|
|
||||||
builder.AppendLine($"using {UsingSystemRuntimeInterop};");
|
|
||||||
builder.AppendLine();
|
|
||||||
builder.AppendLine($"namespace {AttributeNamespace};");
|
|
||||||
builder.AppendLine();
|
|
||||||
builder.AppendLine("[StructLayout(LayoutKind.Sequential, Pack = 1)]");
|
|
||||||
builder.AppendLine($"public unsafe struct {structName}");
|
|
||||||
builder.AppendLine("{");
|
|
||||||
builder.AppendLine($" private const int Size = {size};");
|
|
||||||
builder.AppendLine();
|
|
||||||
builder.AppendLine($" private fixed {targetType} elements[Size];");
|
|
||||||
builder.AppendLine();
|
|
||||||
builder.AppendLine(" public int Length => Size;");
|
|
||||||
builder.AppendLine();
|
|
||||||
builder.AppendLine($" public ref {targetType} this[int index]");
|
|
||||||
builder.AppendLine(" {");
|
|
||||||
builder.AppendLine(" get");
|
|
||||||
builder.AppendLine(" {");
|
|
||||||
builder.AppendLine(" if ((uint)index >= Size) throw new IndexOutOfRangeException();");
|
|
||||||
builder.AppendLine($" return ref this.elements[index];");
|
|
||||||
builder.AppendLine(" }");
|
|
||||||
builder.AppendLine(" }");
|
|
||||||
builder.AppendLine();
|
|
||||||
builder.AppendLine($" public Span<{targetType}> AsSpan()");
|
|
||||||
builder.AppendLine(" {");
|
|
||||||
builder.AppendLine($" fixed ({targetType}* ptr = this.elements)");
|
|
||||||
builder.AppendLine(" {");
|
|
||||||
builder.AppendLine($" return MemoryMarshal.CreateSpan(ref *ptr, Size);");
|
|
||||||
builder.AppendLine(" }");
|
|
||||||
builder.AppendLine(" }");
|
|
||||||
builder.AppendLine();
|
|
||||||
builder.AppendLine($" public readonly ReadOnlySpan<{targetType}> AsReadOnlySpan()");
|
|
||||||
builder.AppendLine(" {");
|
|
||||||
builder.AppendLine($" fixed ({targetType}* ptr = this.elements)");
|
|
||||||
builder.AppendLine(" {");
|
|
||||||
builder.AppendLine($" return new ReadOnlySpan<{targetType}>(ptr, Size);");
|
|
||||||
builder.AppendLine(" }");
|
|
||||||
builder.AppendLine(" }");
|
|
||||||
builder.AppendLine();
|
|
||||||
builder.AppendLine($" public void Set(ReadOnlySpan<{targetType}> value)");
|
|
||||||
builder.AppendLine(" {");
|
|
||||||
builder.AppendLine($" fixed ({targetType}* ptr = this.elements)");
|
|
||||||
builder.AppendLine(" {");
|
|
||||||
builder.AppendLine($" var span = MemoryMarshal.CreateSpan(ref *ptr, Size);");
|
|
||||||
builder.AppendLine(" span.Clear();");
|
|
||||||
builder.AppendLine(" value[..Math.Min(value.Length, Size)].CopyTo(span);");
|
|
||||||
builder.AppendLine(" }");
|
|
||||||
builder.AppendLine(" }");
|
|
||||||
builder.AppendLine();
|
|
||||||
builder.AppendLine($" public void Clear()");
|
|
||||||
builder.AppendLine(" {");
|
|
||||||
builder.AppendLine($" fixed ({targetType}* ptr = this.elements)");
|
|
||||||
builder.AppendLine(" {");
|
|
||||||
builder.AppendLine($" MemoryMarshal.CreateSpan(ref *ptr, Size).Clear();");
|
|
||||||
builder.AppendLine(" }");
|
|
||||||
builder.AppendLine(" }");
|
|
||||||
builder.AppendLine();
|
|
||||||
builder.AppendLine($" public static {structName} From{capitalizedType}Array({targetType}[] array)");
|
|
||||||
builder.AppendLine(" {");
|
|
||||||
builder.AppendLine($" var arr = default({structName});");
|
|
||||||
builder.AppendLine(" arr.Set(array);");
|
|
||||||
builder.AppendLine(" return arr;");
|
|
||||||
builder.AppendLine(" }");
|
|
||||||
builder.AppendLine("}");
|
|
||||||
|
|
||||||
sourceProductionContext.AddSource($"{structName}.g.cs", builder.ToString());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
#nullable disable
|
|
||||||
@@ -1,173 +0,0 @@
|
|||||||
using Microsoft.CodeAnalysis;
|
|
||||||
using Microsoft.CodeAnalysis.CSharp;
|
|
||||||
using Microsoft.CodeAnalysis.CSharp.Syntax;
|
|
||||||
using Sybil;
|
|
||||||
using System.Collections.Immutable;
|
|
||||||
using System.Linq;
|
|
||||||
|
|
||||||
namespace System.Extensions;
|
|
||||||
#nullable enable
|
|
||||||
[Generator(LanguageNames.CSharp)]
|
|
||||||
public class NotifyPropertyChangedGenerator : IIncrementalGenerator
|
|
||||||
{
|
|
||||||
private const string AttributeNamespace = "System.Extensions";
|
|
||||||
private const string AttributeName = "GenerateNotifyPropertyChangedAttribute";
|
|
||||||
private const string AttributeShortName = "GenerateNotifyPropertyChanged";
|
|
||||||
private const string PropertyChangedEventHandler = "PropertyChangedEventHandler";
|
|
||||||
private const string NullablePropertyChangedEventHandler = "PropertyChangedEventHandler?";
|
|
||||||
private const string PropertyChanged = "PropertyChanged";
|
|
||||||
private const string Partial = "partial";
|
|
||||||
private const string Public = "public";
|
|
||||||
|
|
||||||
public void Initialize(IncrementalGeneratorInitializationContext context)
|
|
||||||
{
|
|
||||||
context.RegisterPostInitializationOutput(context =>
|
|
||||||
{
|
|
||||||
var compilationUnitBuilder = SyntaxBuilder.CreateCompilationUnit()
|
|
||||||
.WithNamespace(
|
|
||||||
SyntaxBuilder.CreateNamespace(AttributeNamespace)
|
|
||||||
.WithClass(SyntaxBuilder.CreateClass(AttributeName)
|
|
||||||
.WithModifier(Public)
|
|
||||||
.WithConstructor(SyntaxBuilder.CreateConstructor(AttributeName)
|
|
||||||
.WithModifier(Public))
|
|
||||||
.WithAttribute(SyntaxBuilder.CreateAttribute("AttributeUsage")
|
|
||||||
.WithArgument(AttributeTargets.Field)
|
|
||||||
.WithArgument("Inherited", false)
|
|
||||||
.WithArgument("AllowMultiple", false))
|
|
||||||
.WithBaseClass(nameof(Attribute))));
|
|
||||||
var compilationUnitSyntax = compilationUnitBuilder.Build();
|
|
||||||
var source = compilationUnitSyntax.ToFullString();
|
|
||||||
context.AddSource($"{AttributeName}.g", source);
|
|
||||||
});
|
|
||||||
|
|
||||||
var classDeclarations = context.SyntaxProvider.CreateSyntaxProvider(
|
|
||||||
predicate: static (s, _) => s is FieldDeclarationSyntax,
|
|
||||||
transform: static (ctx, _) => GetFilteredFieldDeclarationSyntax(ctx)).Where(static c => c is not null);
|
|
||||||
|
|
||||||
var compilationAndClasses = context.CompilationProvider.Combine(classDeclarations.Collect());
|
|
||||||
context.RegisterSourceOutput(compilationAndClasses, (sourceProductionContext, tuple) => Execute(tuple.Left, tuple.Right, sourceProductionContext));
|
|
||||||
}
|
|
||||||
|
|
||||||
private static ClassDeclarationSyntax? GetFilteredFieldDeclarationSyntax(GeneratorSyntaxContext context)
|
|
||||||
{
|
|
||||||
var fieldDeclarationSyntax = (FieldDeclarationSyntax)context.Node;
|
|
||||||
if (fieldDeclarationSyntax.AttributeLists
|
|
||||||
.SelectMany(l => l.Attributes)
|
|
||||||
.OfType<AttributeSyntax>()
|
|
||||||
.Any(s => s.Name.ToString() == AttributeName || s.Name.ToString() == AttributeShortName))
|
|
||||||
{
|
|
||||||
return fieldDeclarationSyntax.Parent as ClassDeclarationSyntax;
|
|
||||||
}
|
|
||||||
|
|
||||||
return default;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static void Execute(Compilation compilation, ImmutableArray<ClassDeclarationSyntax?> classes, SourceProductionContext sourceProductionContext)
|
|
||||||
{
|
|
||||||
if (classes.IsDefaultOrEmpty)
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
var maybeLanguageVersion = (compilation.SyntaxTrees.FirstOrDefault()?.Options as CSharpParseOptions)?.LanguageVersion;
|
|
||||||
if (!maybeLanguageVersion.HasValue)
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
var languageVersion = maybeLanguageVersion.Value;
|
|
||||||
var distinctClasses = classes.Distinct();
|
|
||||||
foreach (var c in distinctClasses.OfType<ClassDeclarationSyntax>())
|
|
||||||
{
|
|
||||||
var className = c.Identifier.ToFullString();
|
|
||||||
if (!c.Modifiers.Any(m => m.ToString() == Partial))
|
|
||||||
{
|
|
||||||
Diagnostics.ClassNotPartial(className);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
var originalNamespaceSyntax = GetParentOfType<BaseNamespaceDeclarationSyntax>(c);
|
|
||||||
if (originalNamespaceSyntax is null)
|
|
||||||
{
|
|
||||||
Diagnostics.ClassNotInNamespace(className);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!c.Members
|
|
||||||
.OfType<EventFieldDeclarationSyntax>()
|
|
||||||
.Any(eventField =>
|
|
||||||
(eventField.Declaration.Variables.Any(variable => variable.Identifier.Text == PropertyChanged) &&
|
|
||||||
eventField.Modifiers.Any(modifier => modifier.Text == Public) &&
|
|
||||||
eventField.Declaration.Type.ToString() == PropertyChangedEventHandler) ||
|
|
||||||
eventField.Declaration.Type.ToString() == NullablePropertyChangedEventHandler))
|
|
||||||
{
|
|
||||||
Diagnostics.ClassDoesNotImplementINotifyPropertyChanged(className);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
var generatedClassBuilder = SyntaxBuilder.CreateClass(className)
|
|
||||||
.WithModifiers(string.Join(" ", c.Modifiers.Select(m => m.ToFullString())));
|
|
||||||
var compilationUnitBuilder = SyntaxBuilder.CreateCompilationUnit()
|
|
||||||
.WithNamespace(
|
|
||||||
(languageVersion >= LanguageVersion.CSharp10 ?
|
|
||||||
SyntaxBuilder.CreateFileScopedNamespace(originalNamespaceSyntax.Name.ToFullString()) :
|
|
||||||
SyntaxBuilder.CreateNamespace(originalNamespaceSyntax.Name.ToFullString()))
|
|
||||||
.WithClass(generatedClassBuilder));
|
|
||||||
|
|
||||||
foreach (var field in c.DescendantNodes().OfType<FieldDeclarationSyntax>())
|
|
||||||
{
|
|
||||||
if (field.AttributeLists
|
|
||||||
.SelectMany(l => l.Attributes)
|
|
||||||
.OfType<AttributeSyntax>()
|
|
||||||
.Any(s => s.Name.ToString() == AttributeName ||
|
|
||||||
s.Name.ToString() == AttributeShortName) is false)
|
|
||||||
{
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
var fieldName = field.Declaration.Variables.First().Identifier.ToFullString();
|
|
||||||
var propertyName = fieldName.TrimStart('_');
|
|
||||||
propertyName = $"{char.ToUpper(propertyName[0])}{propertyName.Substring(1)}";
|
|
||||||
var propertyBuilder = SyntaxBuilder.CreateProperty(field.Declaration.Type.ToFullString(), propertyName)
|
|
||||||
.WithModifier(Public)
|
|
||||||
.WithAccessor(SyntaxBuilder.CreateGetter()
|
|
||||||
.WithBody($"return this.{fieldName};"))
|
|
||||||
.WithAccessor(SyntaxBuilder.CreateSetter()
|
|
||||||
.WithBody($"this.{fieldName} = value;\r\nthis.PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof({propertyName})));"));
|
|
||||||
generatedClassBuilder.WithProperty(propertyBuilder);
|
|
||||||
}
|
|
||||||
|
|
||||||
var generatedSyntax = compilationUnitBuilder.Build();
|
|
||||||
if (originalNamespaceSyntax.Usings.Count != 0)
|
|
||||||
{
|
|
||||||
generatedSyntax = generatedSyntax.AddUsings([.. originalNamespaceSyntax.Usings]);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
if (originalNamespaceSyntax.Parent is CompilationUnitSyntax compilationUnit)
|
|
||||||
{
|
|
||||||
generatedSyntax = generatedSyntax.AddUsings([.. compilationUnit.Usings]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var fileSource = $"#nullable enable\n{generatedSyntax.ToFullString()}\n#nullable disable";
|
|
||||||
sourceProductionContext.AddSource($"{className}.g", fileSource);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static T? GetParentOfType<T>(SyntaxNode syntaxNode)
|
|
||||||
{
|
|
||||||
if (syntaxNode.Parent is null)
|
|
||||||
{
|
|
||||||
return default;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (syntaxNode.Parent is T parentNode)
|
|
||||||
{
|
|
||||||
return parentNode;
|
|
||||||
}
|
|
||||||
|
|
||||||
return GetParentOfType<T>(syntaxNode.Parent);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
#nullable disable
|
|
||||||
-30
@@ -1,30 +0,0 @@
|
|||||||
<Project Sdk="Microsoft.NET.Sdk">
|
|
||||||
|
|
||||||
<PropertyGroup>
|
|
||||||
<TargetFramework>netstandard2.0</TargetFramework>
|
|
||||||
<GeneratePackageOnBuild>true</GeneratePackageOnBuild>
|
|
||||||
<Authors>Alexandru Macocian</Authors>
|
|
||||||
<Description>Source generators extensions for netstandard2.0.</Description>
|
|
||||||
<Version>0.1.7</Version>
|
|
||||||
<LangVersion>latest</LangVersion>
|
|
||||||
<EnableNETAnalyzers>true</EnableNETAnalyzers>
|
|
||||||
<EnforceExtendedAnalyzerRules>true</EnforceExtendedAnalyzerRules>
|
|
||||||
<IsRoslynComponent>true</IsRoslynComponent>
|
|
||||||
<CopyLocalLockFileAssemblies>true</CopyLocalLockFileAssemblies>
|
|
||||||
</PropertyGroup>
|
|
||||||
|
|
||||||
<ItemGroup>
|
|
||||||
<PackageReference Include="Microsoft.CodeAnalysis.Analyzers" Version="3.3.4" PrivateAssets="all">
|
|
||||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
|
||||||
</PackageReference>
|
|
||||||
<PackageReference Include="Microsoft.CodeAnalysis.CSharp" Version="4.8.0" PrivateAssets="all" />
|
|
||||||
<PackageReference Include="Sybil" Version="0.8.4" PrivateAssets="all" />
|
|
||||||
</ItemGroup>
|
|
||||||
|
|
||||||
<ItemGroup>
|
|
||||||
<!-- Package the generator in the analyzer directory of the nuget package -->
|
|
||||||
<None Include="$(OutputPath)\$(AssemblyName).dll" Pack="true" PackagePath="analyzers/dotnet/cs" Visible="false" />
|
|
||||||
<None Include="$(OutputPath)\Sybil.dll" Pack="true" PackagePath="analyzers/dotnet/cs" Visible="false" />
|
|
||||||
</ItemGroup>
|
|
||||||
|
|
||||||
</Project>
|
|
||||||
+6
-6
@@ -1,17 +1,17 @@
|
|||||||
<Project Sdk="Microsoft.NET.Sdk">
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<TargetFramework>net10.0</TargetFramework>
|
<TargetFramework>net6.0</TargetFramework>
|
||||||
|
|
||||||
<IsPackable>false</IsPackable>
|
<IsPackable>false</IsPackable>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="FluentAssertions" Version="6.12.0" />
|
<PackageReference Include="FluentAssertions" Version="6.9.0" />
|
||||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.11.0" />
|
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.3.2" />
|
||||||
<PackageReference Include="MSTest.TestAdapter" Version="3.5.2" />
|
<PackageReference Include="MSTest.TestAdapter" Version="2.2.10" />
|
||||||
<PackageReference Include="MSTest.TestFramework" Version="3.5.2" />
|
<PackageReference Include="MSTest.TestFramework" Version="2.2.10" />
|
||||||
<PackageReference Include="coverlet.collector" Version="6.0.2">
|
<PackageReference Include="coverlet.collector" Version="3.1.2">
|
||||||
<PrivateAssets>all</PrivateAssets>
|
<PrivateAssets>all</PrivateAssets>
|
||||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||||
</PackageReference>
|
</PackageReference>
|
||||||
|
|||||||
@@ -9,10 +9,8 @@ public sealed class SecureString
|
|||||||
private byte[] encryptedValue;
|
private byte[] encryptedValue;
|
||||||
|
|
||||||
public string Value {
|
public string Value {
|
||||||
get => this.encryptedValue is not null ? Encoding.UTF8.GetString(ProtectedData.Unprotect(this.encryptedValue, optionalEntropy, DataProtectionScope.CurrentUser)) : string.Empty;
|
get => this.encryptedValue is not null ? Encoding.UTF8.GetString(ProtectedData.Unprotect(this.encryptedValue, optionalEntropy, DataProtectionScope.CurrentUser)) : null;
|
||||||
private set => this.encryptedValue = value is not null
|
private set => this.encryptedValue = ProtectedData.Protect(Encoding.UTF8.GetBytes(value), optionalEntropy, DataProtectionScope.CurrentUser);
|
||||||
? ProtectedData.Protect(Encoding.UTF8.GetBytes(value), optionalEntropy, DataProtectionScope.CurrentUser)
|
|
||||||
: null;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public SecureString(string value)
|
public SecureString(string value)
|
||||||
@@ -44,7 +42,7 @@ public sealed class SecureString
|
|||||||
return this.Value;
|
return this.Value;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static readonly SecureString Empty = new(null);
|
public static readonly SecureString Empty = new(string.Empty);
|
||||||
public static implicit operator string(SecureString ss) => ss is null ? string.Empty : ss.Value;
|
public static implicit operator string(SecureString ss) => ss is null ? string.Empty : ss.Value;
|
||||||
public static implicit operator SecureString(string s) => new(s);
|
public static implicit operator SecureString(string s) => new(s);
|
||||||
public static SecureString operator +(SecureString ss1, SecureString ss2)
|
public static SecureString operator +(SecureString ss1, SecureString ss2)
|
||||||
@@ -81,11 +79,6 @@ public sealed class SecureString
|
|||||||
}
|
}
|
||||||
public static bool operator ==(SecureString ss1, SecureString ss2)
|
public static bool operator ==(SecureString ss1, SecureString ss2)
|
||||||
{
|
{
|
||||||
if (ss1 is null && ss2 is null)
|
|
||||||
{
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
return ss1?.Value == ss2?.Value;
|
return ss1?.Value == ss2?.Value;
|
||||||
}
|
}
|
||||||
public static bool operator !=(SecureString ss1, SecureString ss2)
|
public static bool operator !=(SecureString ss1, SecureString ss2)
|
||||||
|
|||||||
@@ -1,64 +0,0 @@
|
|||||||
using System.IO;
|
|
||||||
using System.Security.Cryptography;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
|
|
||||||
namespace System.Security.Hashing;
|
|
||||||
public abstract class BaseHMACService<T> : IHMACService
|
|
||||||
where T : HMAC
|
|
||||||
{
|
|
||||||
protected abstract T GetHashAlgorithm(byte[] key);
|
|
||||||
|
|
||||||
public async Task<string> Hash(string key, string raw)
|
|
||||||
{
|
|
||||||
using var rawStream = BytesToStream(StringToBytes(raw));
|
|
||||||
var keyBytes = StringToBytes(key);
|
|
||||||
using var hashedStream = await this.HashInternal(keyBytes, rawStream).ConfigureAwait(false);
|
|
||||||
return StreamToString(hashedStream);
|
|
||||||
}
|
|
||||||
public async Task<byte[]> Hash(byte[] key, byte[] raw)
|
|
||||||
{
|
|
||||||
using var rawStream = BytesToStream(raw);
|
|
||||||
using var hashedStream = await this.HashInternal(key, rawStream).ConfigureAwait(false);
|
|
||||||
return StreamToBytes(hashedStream);
|
|
||||||
}
|
|
||||||
public async Task<Stream> Hash(byte[] key, Stream raw)
|
|
||||||
{
|
|
||||||
return await this.HashInternal(key, raw);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static byte[] StreamToBytes(Stream value)
|
|
||||||
{
|
|
||||||
value.Position = 0;
|
|
||||||
var buffer = new byte[1024];
|
|
||||||
using var ms = new MemoryStream();
|
|
||||||
var read = -1;
|
|
||||||
do
|
|
||||||
{
|
|
||||||
read = value.Read(buffer, 0, buffer.Length);
|
|
||||||
ms.Write(buffer, 0, read);
|
|
||||||
} while (read > 0);
|
|
||||||
return ms.ToArray();
|
|
||||||
}
|
|
||||||
private static string StreamToString(Stream value)
|
|
||||||
{
|
|
||||||
return Convert.ToBase64String(StreamToBytes(value));
|
|
||||||
}
|
|
||||||
private static byte[] StringToBytes(string value)
|
|
||||||
{
|
|
||||||
return Convert.FromBase64String(value);
|
|
||||||
}
|
|
||||||
private static Stream BytesToStream(byte[] value)
|
|
||||||
{
|
|
||||||
return new MemoryStream(value);
|
|
||||||
}
|
|
||||||
private async Task<Stream> HashInternal(byte[] key, Stream raw)
|
|
||||||
{
|
|
||||||
if (raw is null)
|
|
||||||
{
|
|
||||||
throw new ArgumentNullException(nameof(raw));
|
|
||||||
}
|
|
||||||
|
|
||||||
using var algo = this.GetHashAlgorithm(key);
|
|
||||||
return await Task.Run(() => new MemoryStream(algo.ComputeHash(raw)));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,63 +0,0 @@
|
|||||||
using System.IO;
|
|
||||||
using System.Security.Cryptography;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
|
|
||||||
namespace System.Security.Hashing;
|
|
||||||
public abstract class BaseHashingService<T>
|
|
||||||
where T : HashAlgorithm
|
|
||||||
{
|
|
||||||
protected abstract T GetHashAlgorithm();
|
|
||||||
|
|
||||||
public async Task<string> Hash(string raw)
|
|
||||||
{
|
|
||||||
using var rawStream = BytesToStream(StringToBytes(raw));
|
|
||||||
using var hashedStream = await this.HashInternal(rawStream).ConfigureAwait(false);
|
|
||||||
return StreamToString(hashedStream);
|
|
||||||
}
|
|
||||||
public async Task<byte[]> Hash(byte[] raw)
|
|
||||||
{
|
|
||||||
using var rawStream = BytesToStream(raw);
|
|
||||||
using var hashedStream = await this.HashInternal(rawStream).ConfigureAwait(false);
|
|
||||||
return StreamToBytes(hashedStream);
|
|
||||||
}
|
|
||||||
public async Task<Stream> Hash(Stream raw)
|
|
||||||
{
|
|
||||||
return await this.HashInternal(raw);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static byte[] StreamToBytes(Stream value)
|
|
||||||
{
|
|
||||||
value.Position = 0;
|
|
||||||
var buffer = new byte[1024];
|
|
||||||
using var ms = new MemoryStream();
|
|
||||||
var read = -1;
|
|
||||||
do
|
|
||||||
{
|
|
||||||
read = value.Read(buffer, 0, buffer.Length);
|
|
||||||
ms.Write(buffer, 0, read);
|
|
||||||
} while (read > 0);
|
|
||||||
return ms.ToArray();
|
|
||||||
}
|
|
||||||
private static string StreamToString(Stream value)
|
|
||||||
{
|
|
||||||
return Convert.ToBase64String(StreamToBytes(value));
|
|
||||||
}
|
|
||||||
private static byte[] StringToBytes(string value)
|
|
||||||
{
|
|
||||||
return Convert.FromBase64String(value);
|
|
||||||
}
|
|
||||||
private static Stream BytesToStream(byte[] value)
|
|
||||||
{
|
|
||||||
return new MemoryStream(value);
|
|
||||||
}
|
|
||||||
private async Task<Stream> HashInternal(Stream raw)
|
|
||||||
{
|
|
||||||
if (raw is null)
|
|
||||||
{
|
|
||||||
throw new ArgumentNullException(nameof(raw));
|
|
||||||
}
|
|
||||||
|
|
||||||
using var algo = this.GetHashAlgorithm();
|
|
||||||
return await Task.Run(() => new MemoryStream(algo.ComputeHash(raw)));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,10 +0,0 @@
|
|||||||
using System.Security.Cryptography;
|
|
||||||
|
|
||||||
namespace System.Security.Hashing;
|
|
||||||
public sealed class HMACMD5Service : BaseHMACService<HMACMD5>, IHMACMD5Service
|
|
||||||
{
|
|
||||||
protected override HMACMD5 GetHashAlgorithm(byte[] key)
|
|
||||||
{
|
|
||||||
return new HMACMD5(key);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,10 +0,0 @@
|
|||||||
using System.Security.Cryptography;
|
|
||||||
|
|
||||||
namespace System.Security.Hashing;
|
|
||||||
public sealed class HMACSha1Service : BaseHMACService<HMACSHA1>, IHMACSha1Service
|
|
||||||
{
|
|
||||||
protected override HMACSHA1 GetHashAlgorithm(byte[] key)
|
|
||||||
{
|
|
||||||
return new HMACSHA1(key);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,10 +0,0 @@
|
|||||||
using System.Security.Cryptography;
|
|
||||||
|
|
||||||
namespace System.Security.Hashing;
|
|
||||||
public sealed class HMACSha256Service : BaseHMACService<HMACSHA256>, IHMACSha256Service
|
|
||||||
{
|
|
||||||
protected override HMACSHA256 GetHashAlgorithm(byte[] key)
|
|
||||||
{
|
|
||||||
return new HMACSHA256(key);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,10 +0,0 @@
|
|||||||
using System.Security.Cryptography;
|
|
||||||
|
|
||||||
namespace System.Security.Hashing;
|
|
||||||
public sealed class HMACSha384Service : BaseHMACService<HMACSHA384>, IHMACSha384Service
|
|
||||||
{
|
|
||||||
protected override HMACSHA384 GetHashAlgorithm(byte[] key)
|
|
||||||
{
|
|
||||||
return new HMACSHA384(key);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,10 +0,0 @@
|
|||||||
using System.Security.Cryptography;
|
|
||||||
|
|
||||||
namespace System.Security.Hashing;
|
|
||||||
public sealed class HMACSha512Service : BaseHMACService<HMACSHA512>, IHMACSha512Service
|
|
||||||
{
|
|
||||||
protected override HMACSHA512 GetHashAlgorithm(byte[] key)
|
|
||||||
{
|
|
||||||
return new HMACSHA512(key);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,4 +0,0 @@
|
|||||||
namespace System.Security.Hashing;
|
|
||||||
public interface IHMACMD5Service : IHMACService
|
|
||||||
{
|
|
||||||
}
|
|
||||||
@@ -1,10 +0,0 @@
|
|||||||
using System.IO;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
|
|
||||||
namespace System.Security.Hashing;
|
|
||||||
public interface IHMACService
|
|
||||||
{
|
|
||||||
Task<string> Hash(string key, string raw);
|
|
||||||
Task<byte[]> Hash(byte[] key, byte[] raw);
|
|
||||||
Task<Stream> Hash(byte[] key, Stream raw);
|
|
||||||
}
|
|
||||||
@@ -1,4 +0,0 @@
|
|||||||
namespace System.Security.Hashing;
|
|
||||||
public interface IHMACSha1Service : IHMACService
|
|
||||||
{
|
|
||||||
}
|
|
||||||
@@ -1,4 +0,0 @@
|
|||||||
namespace System.Security.Hashing;
|
|
||||||
public interface IHMACSha256Service : IHMACService
|
|
||||||
{
|
|
||||||
}
|
|
||||||
@@ -1,4 +0,0 @@
|
|||||||
namespace System.Security.Hashing;
|
|
||||||
public interface IHMACSha384Service : IHMACService
|
|
||||||
{
|
|
||||||
}
|
|
||||||
@@ -1,4 +0,0 @@
|
|||||||
namespace System.Security.Hashing;
|
|
||||||
public interface IHMACSha512Service : IHMACService
|
|
||||||
{
|
|
||||||
}
|
|
||||||
@@ -1,4 +0,0 @@
|
|||||||
namespace System.Security.Hashing;
|
|
||||||
public interface IMD5HashingService : IHashingService
|
|
||||||
{
|
|
||||||
}
|
|
||||||
@@ -1,4 +0,0 @@
|
|||||||
namespace System.Security.Hashing;
|
|
||||||
public interface ISha1HashingService : IHashingService
|
|
||||||
{
|
|
||||||
}
|
|
||||||
@@ -1,4 +0,0 @@
|
|||||||
namespace System.Security.Hashing;
|
|
||||||
public interface ISha256HashingService : IHashingService
|
|
||||||
{
|
|
||||||
}
|
|
||||||
@@ -1,4 +0,0 @@
|
|||||||
namespace System.Security.Hashing;
|
|
||||||
public interface ISha384HashingService : IHashingService
|
|
||||||
{
|
|
||||||
}
|
|
||||||
@@ -1,4 +0,0 @@
|
|||||||
namespace System.Security.Hashing;
|
|
||||||
public interface ISha512HashingService : IHashingService
|
|
||||||
{
|
|
||||||
}
|
|
||||||
@@ -1,10 +0,0 @@
|
|||||||
using System.Security.Cryptography;
|
|
||||||
|
|
||||||
namespace System.Security.Hashing;
|
|
||||||
public sealed class MD5HashingService : BaseHashingService<MD5>, IMD5HashingService
|
|
||||||
{
|
|
||||||
protected override MD5 GetHashAlgorithm()
|
|
||||||
{
|
|
||||||
return MD5.Create();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,7 +0,0 @@
|
|||||||
using System.Security.Cryptography;
|
|
||||||
|
|
||||||
namespace System.Security.Hashing;
|
|
||||||
public sealed class Sha1HashingService : BaseHashingService<SHA1>, ISha1HashingService
|
|
||||||
{
|
|
||||||
protected override SHA1 GetHashAlgorithm() => SHA1.Create();
|
|
||||||
}
|
|
||||||
@@ -1,8 +1,61 @@
|
|||||||
using System.Security.Cryptography;
|
using System.IO;
|
||||||
|
using System.Security.Cryptography;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
namespace System.Security.Hashing;
|
namespace System.Security.Hashing;
|
||||||
|
|
||||||
public sealed class Sha256HashingService : BaseHashingService<SHA256>, ISha256HashingService
|
public sealed class Sha256HashingService : IHashingService
|
||||||
{
|
{
|
||||||
protected override SHA256 GetHashAlgorithm() => SHA256.Create();
|
public async Task<string> Hash(string raw)
|
||||||
|
{
|
||||||
|
using var rawStream = BytesToStream(StringToBytes(raw));
|
||||||
|
using var hashedStream = await HashInternal(rawStream).ConfigureAwait(false);
|
||||||
|
return StreamToString(hashedStream);
|
||||||
|
}
|
||||||
|
public async Task<byte[]> Hash(byte[] raw)
|
||||||
|
{
|
||||||
|
using var rawStream = BytesToStream(raw);
|
||||||
|
using var hashedStream = await HashInternal(rawStream).ConfigureAwait(false);
|
||||||
|
return StreamToBytes(hashedStream);
|
||||||
|
}
|
||||||
|
public async Task<Stream> Hash(Stream raw)
|
||||||
|
{
|
||||||
|
return await HashInternal(raw);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static byte[] StreamToBytes(Stream value)
|
||||||
|
{
|
||||||
|
value.Position = 0;
|
||||||
|
var buffer = new byte[1024];
|
||||||
|
using var ms = new MemoryStream();
|
||||||
|
var read = -1;
|
||||||
|
do
|
||||||
|
{
|
||||||
|
read = value.Read(buffer, 0, buffer.Length);
|
||||||
|
ms.Write(buffer, 0, read);
|
||||||
|
} while (read > 0);
|
||||||
|
return ms.ToArray();
|
||||||
|
}
|
||||||
|
private static string StreamToString(Stream value)
|
||||||
|
{
|
||||||
|
return Convert.ToBase64String(StreamToBytes(value));
|
||||||
|
}
|
||||||
|
private static byte[] StringToBytes(string value)
|
||||||
|
{
|
||||||
|
return Convert.FromBase64String(value);
|
||||||
|
}
|
||||||
|
private static Stream BytesToStream(byte[] value)
|
||||||
|
{
|
||||||
|
return new MemoryStream(value);
|
||||||
|
}
|
||||||
|
private static async Task<Stream> HashInternal(Stream raw)
|
||||||
|
{
|
||||||
|
if (raw is null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(raw));
|
||||||
|
}
|
||||||
|
|
||||||
|
using var sha256 = SHA256.Create();
|
||||||
|
return await Task.Run(() => new MemoryStream(sha256.ComputeHash(raw)));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,10 +0,0 @@
|
|||||||
using System.Security.Cryptography;
|
|
||||||
|
|
||||||
namespace System.Security.Hashing;
|
|
||||||
public sealed class Sha384HashingService : BaseHashingService<SHA384>, ISha384HashingService
|
|
||||||
{
|
|
||||||
protected override SHA384 GetHashAlgorithm()
|
|
||||||
{
|
|
||||||
return SHA384.Create();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,7 +0,0 @@
|
|||||||
using System.Security.Cryptography;
|
|
||||||
|
|
||||||
namespace System.Security.Hashing;
|
|
||||||
public sealed class Sha512HashingService : BaseHashingService<SHA512>, ISha512HashingService
|
|
||||||
{
|
|
||||||
protected override SHA512 GetHashAlgorithm() => SHA512.Create();
|
|
||||||
}
|
|
||||||
@@ -7,7 +7,7 @@
|
|||||||
<PackageLicenseFile>LICENSE</PackageLicenseFile>
|
<PackageLicenseFile>LICENSE</PackageLicenseFile>
|
||||||
<PackageRequireLicenseAcceptance>true</PackageRequireLicenseAcceptance>
|
<PackageRequireLicenseAcceptance>true</PackageRequireLicenseAcceptance>
|
||||||
<RootNamespace>System</RootNamespace>
|
<RootNamespace>System</RootNamespace>
|
||||||
<Version>1.3.1</Version>
|
<Version>1.2.5</Version>
|
||||||
<Authors>Alexandru Macocian</Authors>
|
<Authors>Alexandru Macocian</Authors>
|
||||||
<RepositoryUrl>https://github.com/AlexMacocian/SystemExtensions</RepositoryUrl>
|
<RepositoryUrl>https://github.com/AlexMacocian/SystemExtensions</RepositoryUrl>
|
||||||
<Description>Security extensions for the System namespace</Description>
|
<Description>Security extensions for the System namespace</Description>
|
||||||
@@ -21,7 +21,7 @@
|
|||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="System.Security.Cryptography.ProtectedData" Version="8.0.0" />
|
<PackageReference Include="System.Security.Cryptography.ProtectedData" Version="7.0.1" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
</Project>
|
</Project>
|
||||||
@@ -1,36 +0,0 @@
|
|||||||
using System.Extensions;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
|
|
||||||
namespace System.Cache;
|
|
||||||
public sealed class AsyncValueCache<T>(Func<Task<T>> cacheRefreshOperation, TimeSpan cacheDuration)
|
|
||||||
where T : class
|
|
||||||
{
|
|
||||||
private readonly Func<Task<T>> cacheRefreshOperation = cacheRefreshOperation.ThrowIfNull(nameof(cacheRefreshOperation));
|
|
||||||
private readonly TimeSpan cacheDuration = cacheDuration;
|
|
||||||
|
|
||||||
private T? value;
|
|
||||||
private DateTime lastCacheRefresh = DateTime.MinValue;
|
|
||||||
|
|
||||||
public async Task<T> GetValue()
|
|
||||||
{
|
|
||||||
if (DateTime.Now - this.lastCacheRefresh < this.cacheDuration &&
|
|
||||||
this.value is not null)
|
|
||||||
{
|
|
||||||
return this.value;
|
|
||||||
}
|
|
||||||
|
|
||||||
return await this.PerformCacheRefresh();
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task<T> ForceCacheRefresh()
|
|
||||||
{
|
|
||||||
return await this.PerformCacheRefresh();
|
|
||||||
}
|
|
||||||
|
|
||||||
private async Task<T> PerformCacheRefresh()
|
|
||||||
{
|
|
||||||
this.value = await this.cacheRefreshOperation();
|
|
||||||
this.lastCacheRefresh = DateTime.Now;
|
|
||||||
return this.value;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,35 +0,0 @@
|
|||||||
using System.Extensions;
|
|
||||||
|
|
||||||
namespace System.Cache;
|
|
||||||
public sealed class ValueCache<T>(Func<T> cacheRefreshOperation, TimeSpan cacheDuration)
|
|
||||||
where T : class
|
|
||||||
{
|
|
||||||
private readonly Func<T> cacheRefreshOperation = cacheRefreshOperation.ThrowIfNull(nameof(cacheRefreshOperation));
|
|
||||||
private readonly TimeSpan cacheDuration = cacheDuration;
|
|
||||||
|
|
||||||
private T? value;
|
|
||||||
private DateTime lastCacheRefresh = DateTime.MinValue;
|
|
||||||
|
|
||||||
public T GetValue()
|
|
||||||
{
|
|
||||||
if (DateTime.Now - this.lastCacheRefresh < this.cacheDuration &&
|
|
||||||
this.value is not null)
|
|
||||||
{
|
|
||||||
return this.value;
|
|
||||||
}
|
|
||||||
|
|
||||||
return this.PerformCacheRefresh();
|
|
||||||
}
|
|
||||||
|
|
||||||
public T ForceCacheRefresh()
|
|
||||||
{
|
|
||||||
return this.PerformCacheRefresh();
|
|
||||||
}
|
|
||||||
|
|
||||||
private T PerformCacheRefresh()
|
|
||||||
{
|
|
||||||
this.value = this.cacheRefreshOperation();
|
|
||||||
this.lastCacheRefresh = DateTime.Now;
|
|
||||||
return this.value;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -16,14 +16,14 @@ public sealed class AVLTree<T> : ICollection<T> where T : IComparable<T>
|
|||||||
private class AVLNode<TKey>
|
private class AVLNode<TKey>
|
||||||
{
|
{
|
||||||
public TKey Value;
|
public TKey Value;
|
||||||
public AVLNode<TKey> Left = default!;
|
public AVLNode<TKey> Left;
|
||||||
public AVLNode<TKey> Right = default!;
|
public AVLNode<TKey> Right;
|
||||||
public AVLNode(TKey value)
|
public AVLNode(TKey value)
|
||||||
{
|
{
|
||||||
this.Value = value;
|
this.Value = value;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
AVLNode<T> root = default!;
|
AVLNode<T> root;
|
||||||
private int count = 0;
|
private int count = 0;
|
||||||
private readonly bool isReadOnly = false;
|
private readonly bool isReadOnly = false;
|
||||||
#endregion
|
#endregion
|
||||||
@@ -98,7 +98,7 @@ public sealed class AVLTree<T> : ICollection<T> where T : IComparable<T>
|
|||||||
/// <param name="value">Value to be deleted.</param>
|
/// <param name="value">Value to be deleted.</param>
|
||||||
public bool Remove(T value)
|
public bool Remove(T value)
|
||||||
{
|
{
|
||||||
this.root = this.Delete(this.root, value)!;
|
this.root = this.Delete(this.root, value);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -115,19 +115,19 @@ public sealed class AVLTree<T> : ICollection<T> where T : IComparable<T>
|
|||||||
if (currentNode.Left != null)
|
if (currentNode.Left != null)
|
||||||
{
|
{
|
||||||
queue.Enqueue(currentNode.Left);
|
queue.Enqueue(currentNode.Left);
|
||||||
currentNode.Left = default!;
|
currentNode.Left = null;
|
||||||
this.count--;
|
this.count--;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (currentNode.Right != null)
|
if (currentNode.Right != null)
|
||||||
{
|
{
|
||||||
queue.Enqueue(currentNode.Right);
|
queue.Enqueue(currentNode.Right);
|
||||||
currentNode.Right = default!;
|
currentNode.Right = null;
|
||||||
this.count--;
|
this.count--;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
this.root = default!;
|
this.root = null;
|
||||||
this.count--;
|
this.count--;
|
||||||
}
|
}
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -222,8 +222,9 @@ public sealed class AVLTree<T> : ICollection<T> where T : IComparable<T>
|
|||||||
|
|
||||||
return current;
|
return current;
|
||||||
}
|
}
|
||||||
private AVLNode<T>? Delete(AVLNode<T> current, T target)
|
private AVLNode<T> Delete(AVLNode<T> current, T target)
|
||||||
{
|
{
|
||||||
|
AVLNode<T> parent;
|
||||||
if (current == null)
|
if (current == null)
|
||||||
{ return null; }
|
{ return null; }
|
||||||
else
|
else
|
||||||
@@ -231,7 +232,7 @@ public sealed class AVLTree<T> : ICollection<T> where T : IComparable<T>
|
|||||||
//left subtree
|
//left subtree
|
||||||
if (target.CompareTo(current.Value) < 0)
|
if (target.CompareTo(current.Value) < 0)
|
||||||
{
|
{
|
||||||
current.Left = this.Delete(current.Left, target)!;
|
current.Left = this.Delete(current.Left, target);
|
||||||
if (this.BalanceFactor(current) == -2)//here
|
if (this.BalanceFactor(current) == -2)//here
|
||||||
{
|
{
|
||||||
if (this.BalanceFactor(current.Right) <= 0)
|
if (this.BalanceFactor(current.Right) <= 0)
|
||||||
@@ -247,7 +248,7 @@ public sealed class AVLTree<T> : ICollection<T> where T : IComparable<T>
|
|||||||
//right subtree
|
//right subtree
|
||||||
else if (target.CompareTo(current.Value) > 0)
|
else if (target.CompareTo(current.Value) > 0)
|
||||||
{
|
{
|
||||||
current.Right = this.Delete(current.Right, target)!;
|
current.Right = this.Delete(current.Right, target);
|
||||||
if (this.BalanceFactor(current) == 2)
|
if (this.BalanceFactor(current) == 2)
|
||||||
{
|
{
|
||||||
if (this.BalanceFactor(current.Left) >= 0)
|
if (this.BalanceFactor(current.Left) >= 0)
|
||||||
@@ -267,14 +268,14 @@ public sealed class AVLTree<T> : ICollection<T> where T : IComparable<T>
|
|||||||
if (current.Right != null)
|
if (current.Right != null)
|
||||||
{
|
{
|
||||||
//delete its inorder successor
|
//delete its inorder successor
|
||||||
var parent = current.Right;
|
parent = current.Right;
|
||||||
while (parent.Left != null)
|
while (parent.Left != null)
|
||||||
{
|
{
|
||||||
parent = parent.Left;
|
parent = parent.Left;
|
||||||
}
|
}
|
||||||
|
|
||||||
current.Value = parent.Value;
|
current.Value = parent.Value;
|
||||||
current.Right = this.Delete(current.Right, parent.Value)!;
|
current.Right = this.Delete(current.Right, parent.Value);
|
||||||
if (this.BalanceFactor(current) == 2)//rebalancing
|
if (this.BalanceFactor(current) == 2)//rebalancing
|
||||||
{
|
{
|
||||||
if (this.BalanceFactor(current.Left) >= 0)
|
if (this.BalanceFactor(current.Left) >= 0)
|
||||||
@@ -293,7 +294,7 @@ public sealed class AVLTree<T> : ICollection<T> where T : IComparable<T>
|
|||||||
|
|
||||||
return current;
|
return current;
|
||||||
}
|
}
|
||||||
private AVLNode<T>? Find(T target, AVLNode<T> current)
|
private AVLNode<T> Find(T target, AVLNode<T> current)
|
||||||
{
|
{
|
||||||
if (current == null)
|
if (current == null)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -1,208 +0,0 @@
|
|||||||
using System.Linq;
|
|
||||||
using System.Threading;
|
|
||||||
|
|
||||||
namespace System.Collections.Generic;
|
|
||||||
public sealed class CircularBuffer<T> : IList<T>
|
|
||||||
{
|
|
||||||
private readonly T[] buffer;
|
|
||||||
private int head;
|
|
||||||
private int tail;
|
|
||||||
|
|
||||||
public int Count { get; private set; }
|
|
||||||
public bool IsReadOnly { get; } = false;
|
|
||||||
|
|
||||||
public T this[int index]
|
|
||||||
{
|
|
||||||
get
|
|
||||||
{
|
|
||||||
if (index > this.Count)
|
|
||||||
{
|
|
||||||
throw new ArgumentOutOfRangeException(nameof(index));
|
|
||||||
}
|
|
||||||
|
|
||||||
return this.buffer[(this.head + index) % this.buffer.Length];
|
|
||||||
}
|
|
||||||
set
|
|
||||||
{
|
|
||||||
if (index > this.Count)
|
|
||||||
{
|
|
||||||
throw new ArgumentOutOfRangeException(nameof(index));
|
|
||||||
}
|
|
||||||
|
|
||||||
this.buffer[(this.head + index) % this.buffer.Length] = value;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public CircularBuffer(int capacity)
|
|
||||||
{
|
|
||||||
if (capacity < 1) throw new ArgumentException($"{nameof(capacity)} cannot be smaller than 1");
|
|
||||||
|
|
||||||
this.buffer = new T[capacity];
|
|
||||||
this.head = 0;
|
|
||||||
this.tail = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
public CircularBuffer(T[] buffer, int head, int tail)
|
|
||||||
{
|
|
||||||
if (buffer.Length == 0) throw new ArgumentException($"{nameof(buffer)} cannot be of length 0");
|
|
||||||
|
|
||||||
this.buffer = buffer;
|
|
||||||
this.head = head;
|
|
||||||
this.tail = tail;
|
|
||||||
this.Count = this.tail < this.head ?
|
|
||||||
this.tail + this.buffer.Length - this.head + 1 :
|
|
||||||
this.tail - this.head + 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
public CircularBuffer(T[] buffer)
|
|
||||||
{
|
|
||||||
if (buffer.Length == 0) throw new ArgumentException($"{nameof(buffer)} cannot be of length 0");
|
|
||||||
|
|
||||||
this.buffer = buffer;
|
|
||||||
this.head = 0;
|
|
||||||
this.tail = this.buffer.Length - 1;
|
|
||||||
this.Count = this.buffer.Length;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Add(T item)
|
|
||||||
{
|
|
||||||
if (this.Count == this.buffer.Length)
|
|
||||||
{
|
|
||||||
this.buffer[this.tail] = item;
|
|
||||||
this.tail = (this.tail + 1) % this.buffer.Length;
|
|
||||||
this.head = (this.head + 1) % this.buffer.Length;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
this.buffer[this.tail] = item;
|
|
||||||
this.tail = (this.tail + 1) % this.buffer.Length;
|
|
||||||
this.Count++;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Clear()
|
|
||||||
{
|
|
||||||
this.head = 0;
|
|
||||||
this.tail = 0;
|
|
||||||
this.Count = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void DeepClear()
|
|
||||||
{
|
|
||||||
this.Clear();
|
|
||||||
Array.Clear(this.buffer, 0, this.buffer.Length);
|
|
||||||
}
|
|
||||||
|
|
||||||
public bool Contains(T item)
|
|
||||||
{
|
|
||||||
var comparer = EqualityComparer<T>.Default;
|
|
||||||
foreach(var itItem in this)
|
|
||||||
{
|
|
||||||
if (comparer.Equals(itItem, item))
|
|
||||||
{
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
public bool Remove(T item)
|
|
||||||
{
|
|
||||||
var index = this.head;
|
|
||||||
var comparer = EqualityComparer<T>.Default;
|
|
||||||
for (var i = 0; i < this.Count; i++)
|
|
||||||
{
|
|
||||||
if (comparer.Equals(this.buffer[index % this.buffer.Length], item))
|
|
||||||
{
|
|
||||||
int nextIndex;
|
|
||||||
for (var j = 0; j < this.Count - i - 1; j++)
|
|
||||||
{
|
|
||||||
nextIndex = (index + 1) % this.buffer.Length;
|
|
||||||
this.buffer[index] = this.buffer[nextIndex];
|
|
||||||
index = nextIndex;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.Count--;
|
|
||||||
this.tail = this.tail == 0 ? this.buffer.Length - 1 : this.tail - 1;
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
index = (index + 1) % this.buffer.Length;
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void CopyTo(T[] array, int arrayIndex)
|
|
||||||
{
|
|
||||||
foreach(var item in this)
|
|
||||||
{
|
|
||||||
array[arrayIndex++] = item;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public IEnumerator<T> GetEnumerator()
|
|
||||||
{
|
|
||||||
var index = this.head;
|
|
||||||
for (var i = 0; i < this.Count; i++)
|
|
||||||
{
|
|
||||||
yield return this.buffer[index];
|
|
||||||
index = (index + 1) % this.buffer.Length;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
IEnumerator IEnumerable.GetEnumerator()
|
|
||||||
{
|
|
||||||
return this.GetEnumerator();
|
|
||||||
}
|
|
||||||
|
|
||||||
public int IndexOf(T item)
|
|
||||||
{
|
|
||||||
var comparer = EqualityComparer<T>.Default;
|
|
||||||
for (var i = 0; i < this.Count; i++)
|
|
||||||
{
|
|
||||||
if (comparer.Equals(this[i], item))
|
|
||||||
{
|
|
||||||
return i;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Insert(int index, T item)
|
|
||||||
{
|
|
||||||
if (index < 0 || index > this.Count) throw new ArgumentOutOfRangeException(nameof(index));
|
|
||||||
|
|
||||||
if (this.Count == this.buffer.Length) throw new InvalidOperationException("Cannot insert into a full buffer.");
|
|
||||||
|
|
||||||
if (index == this.Count)
|
|
||||||
{
|
|
||||||
this.Add(item);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
for (var i = this.Count; i > index; i--)
|
|
||||||
{
|
|
||||||
this.buffer[(this.head + i) % this.buffer.Length] = this.buffer[(this.head + i - 1) % this.buffer.Length];
|
|
||||||
}
|
|
||||||
|
|
||||||
this.buffer[(this.head + index) % this.buffer.Length] = item;
|
|
||||||
this.tail = (this.tail + 1) % this.buffer.Length;
|
|
||||||
this.Count++;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void RemoveAt(int index)
|
|
||||||
{
|
|
||||||
if (index < 0 || index >= this.Count) throw new ArgumentOutOfRangeException(nameof(index));
|
|
||||||
|
|
||||||
for (var i = index; i < this.Count - 1; i++)
|
|
||||||
{
|
|
||||||
this.buffer[(this.head + i) % this.buffer.Length] = this.buffer[(this.head + i + 1) % this.buffer.Length];
|
|
||||||
}
|
|
||||||
|
|
||||||
this.tail = (this.tail - 1 + this.buffer.Length) % this.buffer.Length;
|
|
||||||
this.Count--;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -8,7 +8,7 @@
|
|||||||
public sealed class FibonacciHeap<T> : IEnumerable<T> where T : IComparable<T>
|
public sealed class FibonacciHeap<T> : IEnumerable<T> where T : IComparable<T>
|
||||||
{
|
{
|
||||||
#region Fields
|
#region Fields
|
||||||
private FibonacciNode<T> root = default!;
|
private FibonacciNode<T> root;
|
||||||
private int count;
|
private int count;
|
||||||
#endregion
|
#endregion
|
||||||
#region Properties
|
#region Properties
|
||||||
@@ -53,8 +53,8 @@ public sealed class FibonacciHeap<T> : IEnumerable<T> where T : IComparable<T>
|
|||||||
{
|
{
|
||||||
Value = value,
|
Value = value,
|
||||||
Marked = false,
|
Marked = false,
|
||||||
Child = default!,
|
Child = null,
|
||||||
Parent = default!,
|
Parent = null,
|
||||||
Degree = 0
|
Degree = 0
|
||||||
};
|
};
|
||||||
node.Previous = node.Next = node;
|
node.Previous = node.Next = node;
|
||||||
@@ -68,7 +68,7 @@ public sealed class FibonacciHeap<T> : IEnumerable<T> where T : IComparable<T>
|
|||||||
public void Merge(FibonacciHeap<T> otherHeap)
|
public void Merge(FibonacciHeap<T> otherHeap)
|
||||||
{
|
{
|
||||||
this.root = this.Merge(this.root, otherHeap.root);
|
this.root = this.Merge(this.root, otherHeap.root);
|
||||||
otherHeap.root = default!;
|
otherHeap.root = null;
|
||||||
this.count += otherHeap.count;
|
this.count += otherHeap.count;
|
||||||
}
|
}
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -80,7 +80,7 @@ public sealed class FibonacciHeap<T> : IEnumerable<T> where T : IComparable<T>
|
|||||||
var currentRoot = this.root;
|
var currentRoot = this.root;
|
||||||
if (currentRoot != null)
|
if (currentRoot != null)
|
||||||
{
|
{
|
||||||
this.root = this.RemoveMinimum(this.root)!;
|
this.root = this.RemoveMinimum(this.root);
|
||||||
this.count--;
|
this.count--;
|
||||||
return currentRoot.Value;
|
return currentRoot.Value;
|
||||||
}
|
}
|
||||||
@@ -115,8 +115,8 @@ public sealed class FibonacciHeap<T> : IEnumerable<T> where T : IComparable<T>
|
|||||||
{
|
{
|
||||||
this.count = 0;
|
this.count = 0;
|
||||||
this.Remove(this.root);
|
this.Remove(this.root);
|
||||||
this.root.Next = this.root.Previous = this.root.Parent = this.root.Child = default!;
|
this.root.Next = this.root.Previous = this.root.Parent = this.root.Child = null;
|
||||||
this.root = default!;
|
this.root = null;
|
||||||
}
|
}
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Return the heap structure as an array. Array is not sorted other than the
|
/// Return the heap structure as an array. Array is not sorted other than the
|
||||||
@@ -127,7 +127,7 @@ public sealed class FibonacciHeap<T> : IEnumerable<T> where T : IComparable<T>
|
|||||||
{
|
{
|
||||||
if (this.count == 0)
|
if (this.count == 0)
|
||||||
{
|
{
|
||||||
return default!;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
var array = new T[this.count];
|
var array = new T[this.count];
|
||||||
@@ -212,12 +212,12 @@ public sealed class FibonacciHeap<T> : IEnumerable<T> where T : IComparable<T>
|
|||||||
this.Remove(current.Child);
|
this.Remove(current.Child);
|
||||||
if (current.Parent != null)
|
if (current.Parent != null)
|
||||||
{
|
{
|
||||||
current.Parent.Child = default!;
|
current.Parent.Child = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
current = current.Next;
|
current = current.Next;
|
||||||
} while (current != node);
|
} while (current != node);
|
||||||
current.Next = current.Previous = current.Child = current.Parent = default!;
|
current.Next = current.Previous = current.Child = current.Parent = null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -279,7 +279,7 @@ public sealed class FibonacciHeap<T> : IEnumerable<T> where T : IComparable<T>
|
|||||||
do
|
do
|
||||||
{
|
{
|
||||||
current.Marked = false;
|
current.Marked = false;
|
||||||
current.Parent = default!;
|
current.Parent = null;
|
||||||
current = current.Next;
|
current = current.Next;
|
||||||
} while (current != node);
|
} while (current != node);
|
||||||
}
|
}
|
||||||
@@ -288,7 +288,7 @@ public sealed class FibonacciHeap<T> : IEnumerable<T> where T : IComparable<T>
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="node">Root of the provided tree.</param>
|
/// <param name="node">Root of the provided tree.</param>
|
||||||
/// <returns></returns>
|
/// <returns></returns>
|
||||||
private FibonacciNode<T>? RemoveMinimum(FibonacciNode<T> node)
|
private FibonacciNode<T> RemoveMinimum(FibonacciNode<T> node)
|
||||||
{
|
{
|
||||||
this.RemoveParent(node.Child);
|
this.RemoveParent(node.Child);
|
||||||
if (node.Next == node)
|
if (node.Next == node)
|
||||||
@@ -318,7 +318,7 @@ public sealed class FibonacciHeap<T> : IEnumerable<T> where T : IComparable<T>
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
trees[node.Degree] = default!;
|
trees[node.Degree] = null;
|
||||||
if (node.Value.CompareTo(t.Value) < 0)
|
if (node.Value.CompareTo(t.Value) < 0)
|
||||||
{
|
{
|
||||||
t.Previous.Next = t.Next;
|
t.Previous.Next = t.Next;
|
||||||
@@ -379,7 +379,7 @@ public sealed class FibonacciHeap<T> : IEnumerable<T> where T : IComparable<T>
|
|||||||
{
|
{
|
||||||
if (node.Next == node)
|
if (node.Next == node)
|
||||||
{
|
{
|
||||||
node.Parent.Child = default!;
|
node.Parent.Child = null;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@@ -413,13 +413,13 @@ public sealed class FibonacciHeap<T> : IEnumerable<T> where T : IComparable<T>
|
|||||||
{
|
{
|
||||||
root = this.Cut(root, node);
|
root = this.Cut(root, node);
|
||||||
var parent = node.Parent;
|
var parent = node.Parent;
|
||||||
node.Parent = default!;
|
node.Parent = null;
|
||||||
while (parent != null && parent.Marked)
|
while (parent != null && parent.Marked)
|
||||||
{
|
{
|
||||||
root = this.Cut(root, parent);
|
root = this.Cut(root, parent);
|
||||||
node = parent;
|
node = parent;
|
||||||
parent = node.Parent;
|
parent = node.Parent;
|
||||||
node.Parent = default!;
|
node.Parent = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (parent != null && parent.Parent != null)
|
if (parent != null && parent.Parent != null)
|
||||||
@@ -449,7 +449,7 @@ public sealed class FibonacciHeap<T> : IEnumerable<T> where T : IComparable<T>
|
|||||||
var node = root;
|
var node = root;
|
||||||
if (node == null)
|
if (node == null)
|
||||||
{
|
{
|
||||||
return default!;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
do
|
do
|
||||||
@@ -467,7 +467,7 @@ public sealed class FibonacciHeap<T> : IEnumerable<T> where T : IComparable<T>
|
|||||||
|
|
||||||
node = node.Next;
|
node = node.Next;
|
||||||
} while (node != root);
|
} while (node != root);
|
||||||
return default!;
|
return null;
|
||||||
}
|
}
|
||||||
IEnumerator IEnumerable.GetEnumerator()
|
IEnumerator IEnumerable.GetEnumerator()
|
||||||
{
|
{
|
||||||
@@ -479,11 +479,11 @@ public sealed class FibonacciHeap<T> : IEnumerable<T> where T : IComparable<T>
|
|||||||
internal sealed class FibonacciNode<T>
|
internal sealed class FibonacciNode<T>
|
||||||
{
|
{
|
||||||
#region Fields
|
#region Fields
|
||||||
private FibonacciNode<T> previous = default!;
|
private FibonacciNode<T> previous;
|
||||||
private FibonacciNode<T> next = default!;
|
private FibonacciNode<T> next;
|
||||||
private FibonacciNode<T> child = default!;
|
private FibonacciNode<T> child;
|
||||||
private FibonacciNode<T> parent = default!;
|
private FibonacciNode<T> parent;
|
||||||
private T value = default!;
|
private T value;
|
||||||
private int degree;
|
private int degree;
|
||||||
private bool marked;
|
private bool marked;
|
||||||
#endregion
|
#endregion
|
||||||
|
|||||||
@@ -48,7 +48,7 @@ public sealed class SkipList<T> : ICollection<T> where T : IComparable<T>
|
|||||||
{
|
{
|
||||||
this.maxLevel = maxLevel;
|
this.maxLevel = maxLevel;
|
||||||
this.random = new Random();
|
this.random = new Random();
|
||||||
this.head = new NodeSet<T>(default!, maxLevel);
|
this.head = new NodeSet<T>(default, maxLevel);
|
||||||
this.end = this.head;
|
this.end = this.head;
|
||||||
for (var i = 0; i <= maxLevel; i++)
|
for (var i = 0; i <= maxLevel; i++)
|
||||||
{
|
{
|
||||||
@@ -242,7 +242,7 @@ public sealed class SkipList<T> : ICollection<T> where T : IComparable<T>
|
|||||||
return curNode;
|
return curNode;
|
||||||
}
|
}
|
||||||
|
|
||||||
return default!;
|
return null;
|
||||||
}
|
}
|
||||||
#endregion
|
#endregion
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ public sealed class Treap<T> : ICollection<T> where T : IComparable<T>
|
|||||||
{
|
{
|
||||||
public TKey Key;
|
public TKey Key;
|
||||||
public int Priority;
|
public int Priority;
|
||||||
public Node<TKey>? Left, Right;
|
public Node<TKey> Left, Right;
|
||||||
public Node(TKey key, int priority)
|
public Node(TKey key, int priority)
|
||||||
{
|
{
|
||||||
this.Key = key;
|
this.Key = key;
|
||||||
@@ -23,7 +23,7 @@ public sealed class Treap<T> : ICollection<T> where T : IComparable<T>
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
private readonly Random randomGen;
|
private readonly Random randomGen;
|
||||||
private Node<T> root = default!;
|
private Node<T> root;
|
||||||
private int count;
|
private int count;
|
||||||
#endregion
|
#endregion
|
||||||
#region Properties
|
#region Properties
|
||||||
@@ -77,7 +77,7 @@ public sealed class Treap<T> : ICollection<T> where T : IComparable<T>
|
|||||||
public void Clear()
|
public void Clear()
|
||||||
{
|
{
|
||||||
this.Clear(this.root);
|
this.Clear(this.root);
|
||||||
this.root = default!;
|
this.root = null;
|
||||||
this.count = 0;
|
this.count = 0;
|
||||||
}
|
}
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -93,7 +93,7 @@ public sealed class Treap<T> : ICollection<T> where T : IComparable<T>
|
|||||||
/// Returns the treap structure as an ordered array.
|
/// Returns the treap structure as an ordered array.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <returns>Ordered array containing the values stored in the treap.</returns>
|
/// <returns>Ordered array containing the values stored in the treap.</returns>
|
||||||
public T[]? ToArray()
|
public T[] ToArray()
|
||||||
{
|
{
|
||||||
if (this.root != null)
|
if (this.root != null)
|
||||||
{
|
{
|
||||||
@@ -104,7 +104,7 @@ public sealed class Treap<T> : ICollection<T> where T : IComparable<T>
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
return default!;
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -135,16 +135,16 @@ public sealed class Treap<T> : ICollection<T> where T : IComparable<T>
|
|||||||
}
|
}
|
||||||
else if (key.CompareTo(node.Key) <= 0)
|
else if (key.CompareTo(node.Key) <= 0)
|
||||||
{
|
{
|
||||||
node.Left = this.InsertNode(node.Left!, key);
|
node.Left = this.InsertNode(node.Left, key);
|
||||||
if (node.Left!.Priority > node.Priority)
|
if (node.Left.Priority > node.Priority)
|
||||||
{
|
{
|
||||||
node = this.RotateRight(node);
|
node = this.RotateRight(node);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
node.Right = this.InsertNode(node.Right!, key);
|
node.Right = this.InsertNode(node.Right, key);
|
||||||
if (node.Right!.Priority > node.Priority)
|
if (node.Right.Priority > node.Priority)
|
||||||
{
|
{
|
||||||
node = this.RotateLeft(node);
|
node = this.RotateLeft(node);
|
||||||
}
|
}
|
||||||
@@ -156,20 +156,20 @@ public sealed class Treap<T> : ICollection<T> where T : IComparable<T>
|
|||||||
{
|
{
|
||||||
if (node == null)
|
if (node == null)
|
||||||
{
|
{
|
||||||
return node!;
|
return node;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (key.CompareTo(node.Key) < 0)
|
if (key.CompareTo(node.Key) < 0)
|
||||||
{
|
{
|
||||||
node.Left = this.RemoveNode(node.Left!, key);
|
node.Left = this.RemoveNode(node.Left, key);
|
||||||
}
|
}
|
||||||
else if (key.CompareTo(node.Key) > 0)
|
else if (key.CompareTo(node.Key) > 0)
|
||||||
{
|
{
|
||||||
node.Right = this.RemoveNode(node.Right!, key);
|
node.Right = this.RemoveNode(node.Right, key);
|
||||||
}
|
}
|
||||||
else if (node.Left == null)
|
else if (node.Left == null)
|
||||||
{
|
{
|
||||||
node = node.Right!;
|
node = node.Right;
|
||||||
}
|
}
|
||||||
else if (node.Right == null)
|
else if (node.Right == null)
|
||||||
{
|
{
|
||||||
@@ -178,26 +178,26 @@ public sealed class Treap<T> : ICollection<T> where T : IComparable<T>
|
|||||||
else if (node.Left.Priority < node.Right.Priority)
|
else if (node.Left.Priority < node.Right.Priority)
|
||||||
{
|
{
|
||||||
node = this.RotateLeft(node);
|
node = this.RotateLeft(node);
|
||||||
node.Left = this.RemoveNode(node.Left!, key);
|
node.Left = this.RemoveNode(node.Left, key);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
node = this.RotateRight(node);
|
node = this.RotateRight(node);
|
||||||
node.Right = this.RemoveNode(node.Right!, key);
|
node.Right = this.RemoveNode(node.Right, key);
|
||||||
}
|
}
|
||||||
|
|
||||||
return node;
|
return node;
|
||||||
}
|
}
|
||||||
private Node<T> RotateRight(Node<T> node)
|
private Node<T> RotateRight(Node<T> node)
|
||||||
{
|
{
|
||||||
Node<T> temp = node.Left!, temp2 = temp.Right!;
|
Node<T> temp = node.Left, temp2 = temp.Right;
|
||||||
temp.Right = node;
|
temp.Right = node;
|
||||||
node.Left = temp2;
|
node.Left = temp2;
|
||||||
return temp;
|
return temp;
|
||||||
}
|
}
|
||||||
private Node<T> RotateLeft(Node<T> node)
|
private Node<T> RotateLeft(Node<T> node)
|
||||||
{
|
{
|
||||||
Node<T> temp = node.Right!, temp2 = temp.Left!;
|
Node<T> temp = node.Right, temp2 = temp.Left;
|
||||||
temp.Left = node;
|
temp.Left = node;
|
||||||
node.Right = temp2;
|
node.Right = temp2;
|
||||||
return temp;
|
return temp;
|
||||||
@@ -220,26 +220,26 @@ public sealed class Treap<T> : ICollection<T> where T : IComparable<T>
|
|||||||
{
|
{
|
||||||
if (node == null)
|
if (node == null)
|
||||||
{
|
{
|
||||||
return node!;
|
return node;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
if (node.Key.CompareTo(key) < 0)
|
if (node.Key.CompareTo(key) < 0)
|
||||||
{
|
{
|
||||||
var found = this.Find(node.Left!, key);
|
var found = this.Find(node.Left, key);
|
||||||
if (found == null)
|
if (found == null)
|
||||||
{
|
{
|
||||||
found = this.Find(node.Right!, key);
|
found = this.Find(node.Right, key);
|
||||||
}
|
}
|
||||||
|
|
||||||
return found;
|
return found;
|
||||||
}
|
}
|
||||||
else if (node.Key.CompareTo(key) > 0)
|
else if (node.Key.CompareTo(key) > 0)
|
||||||
{
|
{
|
||||||
var found = this.Find(node.Right!, key);
|
var found = this.Find(node.Right, key);
|
||||||
if (found == null)
|
if (found == null)
|
||||||
{
|
{
|
||||||
found = this.Find(node.Left!, key);
|
found = this.Find(node.Left, key);
|
||||||
}
|
}
|
||||||
|
|
||||||
return found;
|
return found;
|
||||||
@@ -254,10 +254,10 @@ public sealed class Treap<T> : ICollection<T> where T : IComparable<T>
|
|||||||
{
|
{
|
||||||
if (node != null)
|
if (node != null)
|
||||||
{
|
{
|
||||||
this.ToArray(node.Left!, ref array, ref index);
|
this.ToArray(node.Left, ref array, ref index);
|
||||||
array[index] = node.Key;
|
array[index] = node.Key;
|
||||||
index++;
|
index++;
|
||||||
this.ToArray(node.Right!, ref array, ref index);
|
this.ToArray(node.Right, ref array, ref index);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
private IEnumerator<T> GetEnumerator(Node<T> currentNode)
|
private IEnumerator<T> GetEnumerator(Node<T> currentNode)
|
||||||
|
|||||||
@@ -1,128 +0,0 @@
|
|||||||
namespace System.Extensions;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// https://stackoverflow.com/questions/641361/base32-decoding
|
|
||||||
/// </summary>
|
|
||||||
public static partial class Convert
|
|
||||||
{
|
|
||||||
public static byte[] FromBase32String(string input)
|
|
||||||
{
|
|
||||||
if (string.IsNullOrEmpty(input))
|
|
||||||
{
|
|
||||||
throw new ArgumentNullException("input");
|
|
||||||
}
|
|
||||||
|
|
||||||
input = input.TrimEnd('='); //remove padding characters
|
|
||||||
var byteCount = input.Length * 5 / 8; //this must be TRUNCATED
|
|
||||||
var returnArray = new byte[byteCount];
|
|
||||||
|
|
||||||
byte curByte = 0, bitsRemaining = 8;
|
|
||||||
int arrayIndex = 0;
|
|
||||||
|
|
||||||
foreach (var c in input)
|
|
||||||
{
|
|
||||||
var cValue = CharToValue(c);
|
|
||||||
int mask;
|
|
||||||
if (bitsRemaining > 5)
|
|
||||||
{
|
|
||||||
mask = cValue << (bitsRemaining - 5);
|
|
||||||
curByte = (byte)(curByte | mask);
|
|
||||||
bitsRemaining -= 5;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
mask = cValue >> (5 - bitsRemaining);
|
|
||||||
curByte = (byte)(curByte | mask);
|
|
||||||
returnArray[arrayIndex++] = curByte;
|
|
||||||
curByte = (byte)(cValue << (3 + bitsRemaining));
|
|
||||||
bitsRemaining += 3;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
//if we didn't end with a full byte
|
|
||||||
if (arrayIndex != byteCount)
|
|
||||||
{
|
|
||||||
returnArray[arrayIndex] = curByte;
|
|
||||||
}
|
|
||||||
|
|
||||||
return returnArray;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static string ToBase32String(byte[] input)
|
|
||||||
{
|
|
||||||
if (input == null || input.Length == 0)
|
|
||||||
{
|
|
||||||
throw new ArgumentNullException("input");
|
|
||||||
}
|
|
||||||
|
|
||||||
var charCount = (int)Math.Ceiling(input.Length / 5d) * 8;
|
|
||||||
var returnArray = new char[charCount];
|
|
||||||
|
|
||||||
byte nextChar = 0, bitsRemaining = 5;
|
|
||||||
var arrayIndex = 0;
|
|
||||||
|
|
||||||
foreach (var b in input)
|
|
||||||
{
|
|
||||||
nextChar = (byte)(nextChar | (b >> (8 - bitsRemaining)));
|
|
||||||
returnArray[arrayIndex++] = ValueToChar(nextChar);
|
|
||||||
|
|
||||||
if (bitsRemaining < 4)
|
|
||||||
{
|
|
||||||
nextChar = (byte)((b >> (3 - bitsRemaining)) & 31);
|
|
||||||
returnArray[arrayIndex++] = ValueToChar(nextChar);
|
|
||||||
bitsRemaining += 5;
|
|
||||||
}
|
|
||||||
|
|
||||||
bitsRemaining -= 3;
|
|
||||||
nextChar = (byte)((b << bitsRemaining) & 31);
|
|
||||||
}
|
|
||||||
|
|
||||||
//if we didn't end with a full char
|
|
||||||
if (arrayIndex != charCount)
|
|
||||||
{
|
|
||||||
returnArray[arrayIndex++] = ValueToChar(nextChar);
|
|
||||||
while (arrayIndex != charCount) returnArray[arrayIndex++] = '='; //padding
|
|
||||||
}
|
|
||||||
|
|
||||||
return new string(returnArray);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static int CharToValue(char c)
|
|
||||||
{
|
|
||||||
var value = (int)c;
|
|
||||||
|
|
||||||
//65-90 == uppercase letters
|
|
||||||
if (value < 91 && value > 64)
|
|
||||||
{
|
|
||||||
return value - 65;
|
|
||||||
}
|
|
||||||
//50-55 == numbers 2-7
|
|
||||||
if (value < 56 && value > 49)
|
|
||||||
{
|
|
||||||
return value - 24;
|
|
||||||
}
|
|
||||||
//97-122 == lowercase letters
|
|
||||||
if (value < 123 && value > 96)
|
|
||||||
{
|
|
||||||
return value - 97;
|
|
||||||
}
|
|
||||||
|
|
||||||
throw new ArgumentException($"Character {c} is not a Base32 character.");
|
|
||||||
}
|
|
||||||
|
|
||||||
private static char ValueToChar(byte b)
|
|
||||||
{
|
|
||||||
if (b < 26)
|
|
||||||
{
|
|
||||||
return (char)(b + 65);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (b < 32)
|
|
||||||
{
|
|
||||||
return (char)(b + 24);
|
|
||||||
}
|
|
||||||
|
|
||||||
throw new ArgumentException($"Byte {b} is not a value Base32 value.");
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -9,9 +9,4 @@ public static class LoggingExtensions
|
|||||||
{
|
{
|
||||||
return ScopedLogger<T>.Create(logger, methodName, flowIdentifier);
|
return ScopedLogger<T>.Create(logger, methodName, flowIdentifier);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static ScopedLogger<T> CreateScopedLogger<T>(this ILogger<T> logger, string methodName)
|
|
||||||
{
|
|
||||||
return ScopedLogger<T>.Create(logger, methodName, string.Empty);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
@@ -1,18 +1,47 @@
|
|||||||
namespace System.Extensions;
|
using Newtonsoft.Json;
|
||||||
|
|
||||||
|
namespace System.Extensions;
|
||||||
|
|
||||||
public static class ObjectExtensions
|
public static class ObjectExtensions
|
||||||
{
|
{
|
||||||
|
public static T Deserialize<T>(this string serialized)
|
||||||
|
where T : class
|
||||||
|
{
|
||||||
|
if (serialized.IsNullOrWhiteSpace())
|
||||||
|
{
|
||||||
|
throw new ArgumentException("Provided serialized string cannot be null or whitespace", nameof(serialized));
|
||||||
|
}
|
||||||
|
|
||||||
|
return JsonConvert.DeserializeObject<T>(serialized);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static string Serialize<T>(this T obj)
|
||||||
|
where T : class
|
||||||
|
{
|
||||||
|
if (obj is null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(obj));
|
||||||
|
}
|
||||||
|
|
||||||
|
return JsonConvert.SerializeObject(obj);
|
||||||
|
}
|
||||||
|
|
||||||
public static T Cast<T>(this object obj)
|
public static T Cast<T>(this object obj)
|
||||||
{
|
{
|
||||||
return (T)obj;
|
return (T)obj;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static T? As<T>(this object obj) where T : class
|
public static T As<T>(this object obj) where T : class
|
||||||
{
|
{
|
||||||
return obj as T;
|
return obj as T;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static T ThrowIfNull<T>([ValidatedNotNull] this T? obj, string name) where T : class
|
public static Optional<T> ToOptional<T>(this T obj)
|
||||||
|
{
|
||||||
|
return Optional.FromValue(obj);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static T ThrowIfNull<T>([ValidatedNotNull] this T obj, string name) where T : class
|
||||||
{
|
{
|
||||||
if (obj is null)
|
if (obj is null)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -0,0 +1,193 @@
|
|||||||
|
namespace System.Extensions;
|
||||||
|
|
||||||
|
public static class Optional
|
||||||
|
{
|
||||||
|
public static Optional<T> None<T>()
|
||||||
|
{
|
||||||
|
return new Optional<T>.None();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Optional<T> FromValue<T>(T value)
|
||||||
|
{
|
||||||
|
if (value == null)
|
||||||
|
{
|
||||||
|
return new Optional<T>.None();
|
||||||
|
}
|
||||||
|
|
||||||
|
return new Optional<T>.Some(value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public abstract class Optional<T> : IEquatable<Optional<T>>
|
||||||
|
{
|
||||||
|
public Optional(T value)
|
||||||
|
{
|
||||||
|
this.Value = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
private T Value { get; }
|
||||||
|
|
||||||
|
public T ExtractValue()
|
||||||
|
{
|
||||||
|
if (this is None)
|
||||||
|
{
|
||||||
|
return default;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return this.Value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
public Optional<T> Do(Action<T> onSome, Action onNone)
|
||||||
|
{
|
||||||
|
if (onSome is null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(onSome));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (onNone is null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(onNone));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this is Some)
|
||||||
|
{
|
||||||
|
onSome.Invoke(this.Value);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
onNone.Invoke();
|
||||||
|
}
|
||||||
|
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
public Optional<T> DoAny(Action<T> onSome = null, Action onNone = null)
|
||||||
|
{
|
||||||
|
if (this is Some)
|
||||||
|
{
|
||||||
|
onSome?.Invoke(this.Value);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
onNone?.Invoke();
|
||||||
|
}
|
||||||
|
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
public Optional<V> Switch<V>(Func<T, V> onSome, Func<V> onNone)
|
||||||
|
{
|
||||||
|
if (onSome is null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException($"{nameof(onSome)}");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (onNone is null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException($"{nameof(onNone)}");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this is Some)
|
||||||
|
{
|
||||||
|
return Optional.FromValue(onSome.Invoke(this.Value));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return Optional.FromValue(onNone.Invoke());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
public Optional<V> SwitchAny<V>(Func<T, V> onSome = null, Func<V> onNone = null)
|
||||||
|
{
|
||||||
|
if (this is Some)
|
||||||
|
{
|
||||||
|
return onSome != null ? Optional.FromValue(onSome.Invoke(this.Value)) : Optional.FromValue(default(V));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return onNone != null ? Optional.FromValue(onNone.Invoke()) : Optional.FromValue(default(V));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public override bool Equals(object obj)
|
||||||
|
{
|
||||||
|
if (obj is Optional<T>)
|
||||||
|
{
|
||||||
|
return this.Equals(obj);
|
||||||
|
}
|
||||||
|
|
||||||
|
return base.Equals(obj);
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool Equals(Optional<T> other)
|
||||||
|
{
|
||||||
|
if (this is Some && other is Some)
|
||||||
|
{
|
||||||
|
return this.As<Some>().Equals(other.As<Some>());
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static implicit operator Optional<T>(T value)
|
||||||
|
{
|
||||||
|
return Optional.FromValue(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public override int GetHashCode()
|
||||||
|
{
|
||||||
|
return this.Value == null ? base.GetHashCode() : this.Value.GetHashCode();
|
||||||
|
}
|
||||||
|
|
||||||
|
internal class Some : Optional<T>, IEquatable<Some>
|
||||||
|
{
|
||||||
|
public Some(T value) : base(value)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public override bool Equals(object obj)
|
||||||
|
{
|
||||||
|
if (obj is Some)
|
||||||
|
{
|
||||||
|
return this.Equals(obj.As<Some>());
|
||||||
|
}
|
||||||
|
|
||||||
|
return base.Equals(obj);
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool Equals(Some other)
|
||||||
|
{
|
||||||
|
if (other is None)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (other is Some)
|
||||||
|
{
|
||||||
|
return this.Value.Equals(other.Value);
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static bool operator ==(Some left, Some right)
|
||||||
|
{
|
||||||
|
return left?.Equals(right) == true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static bool operator !=(Some left, Some right)
|
||||||
|
{
|
||||||
|
return left?.Equals(right) != true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override int GetHashCode()
|
||||||
|
{
|
||||||
|
return this.Value is object ? this.Value.GetHashCode() : this.As<object>().GetHashCode();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
internal class None : Optional<T>
|
||||||
|
{
|
||||||
|
public None() : base(default)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,216 @@
|
|||||||
|
namespace System.Extensions;
|
||||||
|
|
||||||
|
public class Result<TSuccess, TFailure>
|
||||||
|
{
|
||||||
|
private readonly object value;
|
||||||
|
|
||||||
|
public Result(TSuccess successValue)
|
||||||
|
{
|
||||||
|
this.value = successValue;
|
||||||
|
}
|
||||||
|
public Result(TFailure failureValue)
|
||||||
|
{
|
||||||
|
this.value = failureValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool TryExtractSuccess(out TSuccess successValue)
|
||||||
|
{
|
||||||
|
|
||||||
|
if (this.value is TSuccess success)
|
||||||
|
{
|
||||||
|
successValue = success;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
successValue = default;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
public bool TryExtractFailure(out TFailure failureValue)
|
||||||
|
{
|
||||||
|
|
||||||
|
if (this.value is TFailure failure)
|
||||||
|
{
|
||||||
|
failureValue = failure;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
failureValue = default;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
public Result<TSuccess, TFailure> Do(Action onSuccess, Action onFailure)
|
||||||
|
{
|
||||||
|
if (onSuccess is null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(onSuccess));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (onFailure is null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(onFailure));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.value is TSuccess)
|
||||||
|
{
|
||||||
|
onSuccess.Invoke();
|
||||||
|
}
|
||||||
|
else if (this.value is TFailure)
|
||||||
|
{
|
||||||
|
onFailure.Invoke();
|
||||||
|
}
|
||||||
|
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
public Result<TSuccess, TFailure> DoAny(Action onSuccess = null, Action onFailure = null)
|
||||||
|
{
|
||||||
|
if (this.value is TSuccess)
|
||||||
|
{
|
||||||
|
onSuccess?.Invoke();
|
||||||
|
}
|
||||||
|
else if (this.value is TFailure)
|
||||||
|
{
|
||||||
|
onFailure?.Invoke();
|
||||||
|
}
|
||||||
|
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
public Result<TSuccess, TFailure> Do(Action<TSuccess> onSuccess, Action<TFailure> onFailure)
|
||||||
|
{
|
||||||
|
if (onSuccess is null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(onSuccess));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (onFailure is null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(onFailure));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.value is TSuccess success)
|
||||||
|
{
|
||||||
|
onSuccess.Invoke(success);
|
||||||
|
}
|
||||||
|
else if (this.value is TFailure failure)
|
||||||
|
{
|
||||||
|
onFailure.Invoke(failure);
|
||||||
|
}
|
||||||
|
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
public Result<TSuccess, TFailure> DoAny(Action<TSuccess> onSuccess = null, Action<TFailure> onFailure = null)
|
||||||
|
{
|
||||||
|
if (this.value is TSuccess success)
|
||||||
|
{
|
||||||
|
onSuccess?.Invoke(success);
|
||||||
|
}
|
||||||
|
else if (this.value is TFailure failure)
|
||||||
|
{
|
||||||
|
onFailure?.Invoke(failure);
|
||||||
|
}
|
||||||
|
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
public T Switch<T>(Func<TSuccess, T> onSuccess, Func<TFailure, T> onFailure)
|
||||||
|
{
|
||||||
|
if (onSuccess is null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(onSuccess));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (onFailure is null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(onFailure));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.value is TSuccess success)
|
||||||
|
{
|
||||||
|
return onSuccess.Invoke(success);
|
||||||
|
}
|
||||||
|
else if (this.value is TFailure failure)
|
||||||
|
{
|
||||||
|
return onFailure.Invoke(failure);
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new InvalidOperationException($"{nameof(this.value)} must be of type {typeof(TSuccess)} or {typeof(TFailure)} in order to switch to {typeof(T)}");
|
||||||
|
}
|
||||||
|
public T SwitchAny<T>(Func<TSuccess, T> onSuccess = null, Func<TFailure, T> onFailure = null)
|
||||||
|
{
|
||||||
|
if (this.value is TSuccess success)
|
||||||
|
{
|
||||||
|
return onSuccess is null ? default : onSuccess.Invoke(success);
|
||||||
|
}
|
||||||
|
else if (this.value is TFailure failure)
|
||||||
|
{
|
||||||
|
return onFailure is null ? default : onFailure.Invoke(failure);
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new InvalidOperationException($"{nameof(this.value)} must be of type {typeof(TSuccess)} or {typeof(TFailure)} in order to switch to {typeof(T)}");
|
||||||
|
}
|
||||||
|
public Result<V, K> Switch<V, K>(Func<TSuccess, V> onSuccess, Func<TFailure, K> onFailure)
|
||||||
|
{
|
||||||
|
if (onSuccess is null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(onSuccess));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (onFailure is null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(onFailure));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.value is TSuccess success)
|
||||||
|
{
|
||||||
|
return Result<V, K>.Success(onSuccess.Invoke(success));
|
||||||
|
}
|
||||||
|
else if (this.value is TFailure failure)
|
||||||
|
{
|
||||||
|
return Result<V, K>.Failure(onFailure.Invoke(failure));
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new InvalidOperationException($"{nameof(this.value)} must be of type {typeof(TSuccess)} or {typeof(TFailure)} in order to switch to Result of type {typeof(V)} or {typeof(K)}");
|
||||||
|
}
|
||||||
|
public Result<V, K> SwitchAny<V, K>(Func<TSuccess, V> onSuccess, Func<TFailure, K> onFailure)
|
||||||
|
{
|
||||||
|
if (this.value is TSuccess success)
|
||||||
|
{
|
||||||
|
return Result<V, K>.Success(onSuccess is null ? default : onSuccess.Invoke(success));
|
||||||
|
}
|
||||||
|
else if (this.value is TFailure failure)
|
||||||
|
{
|
||||||
|
return Result<V, K>.Failure(onFailure is null ? default : onFailure.Invoke(failure));
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new InvalidOperationException($"{nameof(this.value)} must be of type {typeof(TSuccess)} or {typeof(TFailure)} in order to switch to Result of type {typeof(V)} or {typeof(K)}");
|
||||||
|
}
|
||||||
|
public Optional<TSuccess> ToOptional()
|
||||||
|
{
|
||||||
|
if (this.value is TSuccess)
|
||||||
|
{
|
||||||
|
return Optional.FromValue(this.value.Cast<TSuccess>());
|
||||||
|
}
|
||||||
|
|
||||||
|
return Optional.None<TSuccess>();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static implicit operator Result<TSuccess, TFailure>(TSuccess success)
|
||||||
|
{
|
||||||
|
return Success(success);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static implicit operator Result<TSuccess, TFailure>(TFailure failure)
|
||||||
|
{
|
||||||
|
return Failure(failure);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Result<TSuccess, TFailure> Success(TSuccess value)
|
||||||
|
{
|
||||||
|
return new Result<TSuccess, TFailure>(value);
|
||||||
|
}
|
||||||
|
public static Result<TSuccess, TFailure> Failure(TFailure value)
|
||||||
|
{
|
||||||
|
return new Result<TSuccess, TFailure>(value);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,16 +0,0 @@
|
|||||||
using System.Threading;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
|
|
||||||
namespace System.Extensions;
|
|
||||||
public static class SemaphoreSlimExtensions
|
|
||||||
{
|
|
||||||
public static async Task<SemaphoreSlimContext> Acquire(this SemaphoreSlim semaphore)
|
|
||||||
{
|
|
||||||
return await SemaphoreSlimContext.Create(semaphore);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static async Task<SemaphoreSlimContext> Acquire(this SemaphoreSlim semaphore, CancellationToken cancellationToken)
|
|
||||||
{
|
|
||||||
return await SemaphoreSlimContext.Create(semaphore, cancellationToken);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -5,6 +5,27 @@ namespace System.Extensions;
|
|||||||
|
|
||||||
public static class StreamExtensions
|
public static class StreamExtensions
|
||||||
{
|
{
|
||||||
|
public static void DoWhileReading(this Stream stream, Action<int, byte[]> action, int bufferLength = 256)
|
||||||
|
{
|
||||||
|
if (stream is null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(stream));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (action is null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(action));
|
||||||
|
}
|
||||||
|
|
||||||
|
var buffer = new byte[bufferLength];
|
||||||
|
var read = stream.Read(buffer, 0, bufferLength);
|
||||||
|
while(read > 0)
|
||||||
|
{
|
||||||
|
action(bufferLength, buffer);
|
||||||
|
read = stream.Read(buffer, 0, bufferLength);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public static byte[] ReadBytes(this Stream stream, int count)
|
public static byte[] ReadBytes(this Stream stream, int count)
|
||||||
{
|
{
|
||||||
if (stream is null)
|
if (stream is null)
|
||||||
|
|||||||
@@ -0,0 +1,185 @@
|
|||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Threading;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace System.Extensions;
|
||||||
|
|
||||||
|
public static class TaskExtensions
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Mark task as <see cref="TaskCreationOptions.LongRunning"/>.
|
||||||
|
/// </summary>
|
||||||
|
/// <typeparam name="T">Result type.</typeparam>
|
||||||
|
/// <param name="task">Task.</param>
|
||||||
|
/// <returns>The started <see cref="Task{TResult}"/></returns>
|
||||||
|
public static Task<T> LongRunning<T>(this Task<T> task)
|
||||||
|
{
|
||||||
|
return Task.Factory.StartNew(async () =>
|
||||||
|
{
|
||||||
|
return await task;
|
||||||
|
}, TaskCreationOptions.LongRunning).Unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Mark task as <see cref="TaskCreationOptions.LongRunning"/>.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="task">Task.</param>
|
||||||
|
/// <returns>The started <see cref="Task"/></returns>
|
||||||
|
public static Task LongRunning(this Task task)
|
||||||
|
{
|
||||||
|
return Task.Factory.StartNew(async () =>
|
||||||
|
{
|
||||||
|
await task;
|
||||||
|
}, TaskCreationOptions.LongRunning).Unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Execute action periodically and asynchronously.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="onTick">Action to be executed.</param>
|
||||||
|
/// <param name="dueTime">Time to pass before starting to execute.</param>
|
||||||
|
/// <param name="interval">Interval between procs.</param>
|
||||||
|
/// <param name="token">Cancellation token used to cancel the cycle.</param>
|
||||||
|
/// <returns></returns>
|
||||||
|
public static async Task RunPeriodicAsync(this Action onTick, TimeSpan dueTime, TimeSpan interval, CancellationToken token)
|
||||||
|
{
|
||||||
|
if (dueTime > TimeSpan.Zero)
|
||||||
|
{
|
||||||
|
await Task.Delay(dueTime, token).ConfigureAwait(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
while (!token.IsCancellationRequested)
|
||||||
|
{
|
||||||
|
onTick?.Invoke();
|
||||||
|
if (interval > TimeSpan.Zero)
|
||||||
|
{
|
||||||
|
await Task.Delay(interval, token).ConfigureAwait(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Execute's an async Task<T> method which has a void return value synchronously
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="task">Task<T> method to execute</param>
|
||||||
|
public static void RunSync(this Func<Task> task)
|
||||||
|
{
|
||||||
|
var oldContext = SynchronizationContext.Current;
|
||||||
|
var synch = new ExclusiveSynchronizationContext();
|
||||||
|
SynchronizationContext.SetSynchronizationContext(synch);
|
||||||
|
synch.Post(async _ =>
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
await task();
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
synch.InnerException = e;
|
||||||
|
throw;
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
synch.EndMessageLoop();
|
||||||
|
}
|
||||||
|
}, null);
|
||||||
|
synch.BeginMessageLoop();
|
||||||
|
|
||||||
|
SynchronizationContext.SetSynchronizationContext(oldContext);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Execute's an async Task<T> method which has a T return type synchronously
|
||||||
|
/// </summary>
|
||||||
|
/// <typeparam name="T">Return Type</typeparam>
|
||||||
|
/// <param name="task">Task<T> method to execute</param>
|
||||||
|
/// <returns></returns>
|
||||||
|
public static T RunSync<T>(this Func<Task<T>> task)
|
||||||
|
{
|
||||||
|
var oldContext = SynchronizationContext.Current;
|
||||||
|
var synch = new ExclusiveSynchronizationContext();
|
||||||
|
SynchronizationContext.SetSynchronizationContext(synch);
|
||||||
|
var ret = default(T);
|
||||||
|
synch.Post(async _ =>
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
ret = await task();
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
synch.InnerException = e;
|
||||||
|
throw;
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
synch.EndMessageLoop();
|
||||||
|
}
|
||||||
|
}, null);
|
||||||
|
synch.BeginMessageLoop();
|
||||||
|
SynchronizationContext.SetSynchronizationContext(oldContext);
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
private class ExclusiveSynchronizationContext : SynchronizationContext
|
||||||
|
{
|
||||||
|
private bool done;
|
||||||
|
public Exception InnerException { get; set; }
|
||||||
|
readonly AutoResetEvent workItemsWaiting = new AutoResetEvent(false);
|
||||||
|
readonly Queue<Tuple<SendOrPostCallback, object>> items =
|
||||||
|
new Queue<Tuple<SendOrPostCallback, object>>();
|
||||||
|
|
||||||
|
public override void Send(SendOrPostCallback d, object state)
|
||||||
|
{
|
||||||
|
throw new NotSupportedException("We cannot send to our same thread");
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void Post(SendOrPostCallback d, object state)
|
||||||
|
{
|
||||||
|
lock (this.items)
|
||||||
|
{
|
||||||
|
this.items.Enqueue(Tuple.Create(d, state));
|
||||||
|
}
|
||||||
|
|
||||||
|
this.workItemsWaiting.Set();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void EndMessageLoop()
|
||||||
|
{
|
||||||
|
this.Post(_ => this.done = true, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void BeginMessageLoop()
|
||||||
|
{
|
||||||
|
while (!this.done)
|
||||||
|
{
|
||||||
|
Tuple<SendOrPostCallback, object> task = null;
|
||||||
|
lock (this.items)
|
||||||
|
{
|
||||||
|
if (this.items.Count > 0)
|
||||||
|
{
|
||||||
|
task = this.items.Dequeue();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (task != null)
|
||||||
|
{
|
||||||
|
task.Item1(task.Item2);
|
||||||
|
if (this.InnerException != null) // the method threw an exeption
|
||||||
|
{
|
||||||
|
throw new AggregateException("AsyncHelpers.Run method threw an exception.", this.InnerException);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
this.workItemsWaiting.WaitOne();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public override SynchronizationContext CreateCopy()
|
||||||
|
{
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,5 +1,4 @@
|
|||||||
using System.Extensions;
|
using System.IO;
|
||||||
using System.IO;
|
|
||||||
using System.Net.Http.Headers;
|
using System.Net.Http.Headers;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
@@ -8,7 +7,6 @@ namespace System.Net.Http;
|
|||||||
|
|
||||||
public sealed class HttpClient<Tscope> : IHttpClient<Tscope>, IDisposable
|
public sealed class HttpClient<Tscope> : IHttpClient<Tscope>, IDisposable
|
||||||
{
|
{
|
||||||
private readonly bool disposeInnerClient = true;
|
|
||||||
private readonly HttpClient httpClient;
|
private readonly HttpClient httpClient;
|
||||||
private readonly Type scope;
|
private readonly Type scope;
|
||||||
private EventHandler<HttpClientEventMessage> eventEmitted;
|
private EventHandler<HttpClientEventMessage> eventEmitted;
|
||||||
@@ -21,9 +19,7 @@ public sealed class HttpClient<Tscope> : IHttpClient<Tscope>, IDisposable
|
|||||||
}
|
}
|
||||||
remove
|
remove
|
||||||
{
|
{
|
||||||
#pragma warning disable CS8601 // Possible null reference assignment.
|
|
||||||
this.eventEmitted -= value;
|
this.eventEmitted -= value;
|
||||||
#pragma warning restore CS8601 // Possible null reference assignment.
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
public Uri BaseAddress { get => this.httpClient.BaseAddress; set => this.httpClient.BaseAddress = value; }
|
public Uri BaseAddress { get => this.httpClient.BaseAddress; set => this.httpClient.BaseAddress = value; }
|
||||||
@@ -31,213 +27,188 @@ public sealed class HttpClient<Tscope> : IHttpClient<Tscope>, IDisposable
|
|||||||
public long MaxResponseContentBufferSize { get => this.httpClient.MaxResponseContentBufferSize; set => this.httpClient.MaxResponseContentBufferSize = value; }
|
public long MaxResponseContentBufferSize { get => this.httpClient.MaxResponseContentBufferSize; set => this.httpClient.MaxResponseContentBufferSize = value; }
|
||||||
public TimeSpan Timeout { get => this.httpClient.Timeout; set => this.httpClient.Timeout = value; }
|
public TimeSpan Timeout { get => this.httpClient.Timeout; set => this.httpClient.Timeout = value; }
|
||||||
|
|
||||||
#pragma warning disable CS8618 // Non-nullable field must contain a non-null value when exiting constructor. Consider declaring as nullable.
|
public HttpClient()
|
||||||
public HttpClient(bool disposeInnerClient = true)
|
|
||||||
#pragma warning restore CS8618 // Non-nullable field must contain a non-null value when exiting constructor. Consider declaring as nullable.
|
|
||||||
{
|
{
|
||||||
this.disposeInnerClient = disposeInnerClient;
|
|
||||||
this.httpClient = new HttpClient();
|
this.httpClient = new HttpClient();
|
||||||
this.scope = typeof(Tscope);
|
this.scope = typeof(Tscope);
|
||||||
}
|
}
|
||||||
|
|
||||||
#pragma warning disable CS8618 // Non-nullable field must contain a non-null value when exiting constructor. Consider declaring as nullable.
|
|
||||||
public HttpClient(
|
public HttpClient(
|
||||||
#pragma warning restore CS8618 // Non-nullable field must contain a non-null value when exiting constructor. Consider declaring as nullable.
|
HttpMessageHandler handler)
|
||||||
HttpMessageHandler handler,
|
|
||||||
bool disposeInnerClient = true)
|
|
||||||
{
|
{
|
||||||
this.disposeInnerClient = disposeInnerClient;
|
|
||||||
this.httpClient = new HttpClient(handler);
|
this.httpClient = new HttpClient(handler);
|
||||||
this.scope = typeof(Tscope);
|
this.scope = typeof(Tscope);
|
||||||
}
|
}
|
||||||
|
|
||||||
#pragma warning disable CS8618 // Non-nullable field must contain a non-null value when exiting constructor. Consider declaring as nullable.
|
|
||||||
public HttpClient(
|
public HttpClient(
|
||||||
#pragma warning restore CS8618 // Non-nullable field must contain a non-null value when exiting constructor. Consider declaring as nullable.
|
|
||||||
HttpMessageHandler handler,
|
HttpMessageHandler handler,
|
||||||
bool disposeHandler,
|
bool disposeHandler)
|
||||||
bool disposeInnerClient = true)
|
|
||||||
{
|
{
|
||||||
this.disposeInnerClient = disposeInnerClient;
|
|
||||||
this.httpClient = new HttpClient(handler, disposeHandler);
|
this.httpClient = new HttpClient(handler, disposeHandler);
|
||||||
this.scope = typeof(Tscope);
|
this.scope = typeof(Tscope);
|
||||||
}
|
}
|
||||||
|
|
||||||
#pragma warning disable CS8618 // Non-nullable field must contain a non-null value when exiting constructor. Consider declaring as nullable.
|
|
||||||
public HttpClient(
|
|
||||||
#pragma warning restore CS8618 // Non-nullable field must contain a non-null value when exiting constructor. Consider declaring as nullable.
|
|
||||||
HttpClient httpClient,
|
|
||||||
bool disposeInnerClient = true)
|
|
||||||
{
|
|
||||||
this.disposeInnerClient = disposeInnerClient;
|
|
||||||
this.httpClient = httpClient.ThrowIfNull(nameof(httpClient));
|
|
||||||
this.scope = typeof(Tscope);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void CancelPendingRequests()
|
public void CancelPendingRequests()
|
||||||
{
|
{
|
||||||
this.LogDebug(string.Empty, "Canceling request");
|
this.LogInformation(string.Empty, "Canceling request");
|
||||||
this.httpClient.CancelPendingRequests();
|
this.httpClient.CancelPendingRequests();
|
||||||
}
|
}
|
||||||
public Task<HttpResponseMessage> DeleteAsync(string requestUri)
|
public Task<HttpResponseMessage> DeleteAsync(string requestUri)
|
||||||
{
|
{
|
||||||
this.LogDebug(requestUri, nameof(DeleteAsync));
|
this.LogInformation(requestUri, nameof(DeleteAsync));
|
||||||
return this.httpClient.DeleteAsync(requestUri);
|
return this.httpClient.DeleteAsync(requestUri);
|
||||||
}
|
}
|
||||||
public Task<HttpResponseMessage> DeleteAsync(string requestUri, CancellationToken cancellationToken)
|
public Task<HttpResponseMessage> DeleteAsync(string requestUri, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
this.LogDebug(requestUri, nameof(DeleteAsync));
|
this.LogInformation(requestUri, nameof(DeleteAsync));
|
||||||
return this.httpClient.DeleteAsync(requestUri, cancellationToken);
|
return this.httpClient.DeleteAsync(requestUri, cancellationToken);
|
||||||
}
|
}
|
||||||
public Task<HttpResponseMessage> DeleteAsync(Uri requestUri)
|
public Task<HttpResponseMessage> DeleteAsync(Uri requestUri)
|
||||||
{
|
{
|
||||||
this.LogDebug(requestUri.ToString(), nameof(DeleteAsync));
|
this.LogInformation(requestUri.ToString(), nameof(DeleteAsync));
|
||||||
return this.httpClient.DeleteAsync(requestUri);
|
return this.httpClient.DeleteAsync(requestUri);
|
||||||
}
|
}
|
||||||
public Task<HttpResponseMessage> DeleteAsync(Uri requestUri, CancellationToken cancellationToken)
|
public Task<HttpResponseMessage> DeleteAsync(Uri requestUri, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
this.LogDebug(requestUri.ToString(), nameof(DeleteAsync));
|
this.LogInformation(requestUri.ToString(), nameof(DeleteAsync));
|
||||||
return this.httpClient.DeleteAsync(requestUri, cancellationToken);
|
return this.httpClient.DeleteAsync(requestUri, cancellationToken);
|
||||||
}
|
}
|
||||||
public Task<HttpResponseMessage> GetAsync(string requestUri)
|
public Task<HttpResponseMessage> GetAsync(string requestUri)
|
||||||
{
|
{
|
||||||
this.LogDebug(requestUri, nameof(GetAsync));
|
this.LogInformation(requestUri, nameof(GetAsync));
|
||||||
return this.httpClient.GetAsync(requestUri);
|
return this.httpClient.GetAsync(requestUri);
|
||||||
}
|
}
|
||||||
public Task<HttpResponseMessage> GetAsync(string requestUri, HttpCompletionOption completionOption)
|
public Task<HttpResponseMessage> GetAsync(string requestUri, HttpCompletionOption completionOption)
|
||||||
{
|
{
|
||||||
this.LogDebug(requestUri, nameof(GetAsync));
|
this.LogInformation(requestUri, nameof(GetAsync));
|
||||||
return this.httpClient.GetAsync(requestUri, completionOption);
|
return this.httpClient.GetAsync(requestUri, completionOption);
|
||||||
}
|
}
|
||||||
public Task<HttpResponseMessage> GetAsync(string requestUri, HttpCompletionOption completionOption, CancellationToken cancellationToken)
|
public Task<HttpResponseMessage> GetAsync(string requestUri, HttpCompletionOption completionOption, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
this.LogDebug(requestUri, nameof(GetAsync));
|
this.LogInformation(requestUri, nameof(GetAsync));
|
||||||
return this.httpClient.GetAsync(requestUri, completionOption, cancellationToken);
|
return this.httpClient.GetAsync(requestUri, completionOption, cancellationToken);
|
||||||
}
|
}
|
||||||
public Task<HttpResponseMessage> GetAsync(string requestUri, CancellationToken cancellationToken)
|
public Task<HttpResponseMessage> GetAsync(string requestUri, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
this.LogDebug(requestUri, nameof(GetAsync));
|
this.LogInformation(requestUri, nameof(GetAsync));
|
||||||
return this.httpClient.GetAsync(requestUri, cancellationToken);
|
return this.httpClient.GetAsync(requestUri, cancellationToken);
|
||||||
}
|
}
|
||||||
public Task<HttpResponseMessage> GetAsync(Uri requestUri)
|
public Task<HttpResponseMessage> GetAsync(Uri requestUri)
|
||||||
{
|
{
|
||||||
this.LogDebug(requestUri.ToString(), nameof(GetAsync));
|
this.LogInformation(requestUri.ToString(), nameof(GetAsync));
|
||||||
return this.httpClient.GetAsync(requestUri);
|
return this.httpClient.GetAsync(requestUri);
|
||||||
}
|
}
|
||||||
public Task<HttpResponseMessage> GetAsync(Uri requestUri, HttpCompletionOption completionOption)
|
public Task<HttpResponseMessage> GetAsync(Uri requestUri, HttpCompletionOption completionOption)
|
||||||
{
|
{
|
||||||
this.LogDebug(requestUri.ToString(), nameof(GetAsync));
|
this.LogInformation(requestUri.ToString(), nameof(GetAsync));
|
||||||
return this.httpClient.GetAsync(requestUri, completionOption);
|
return this.httpClient.GetAsync(requestUri, completionOption);
|
||||||
}
|
}
|
||||||
public Task<HttpResponseMessage> GetAsync(Uri requestUri, HttpCompletionOption completionOption, CancellationToken cancellationToken)
|
public Task<HttpResponseMessage> GetAsync(Uri requestUri, HttpCompletionOption completionOption, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
this.LogDebug(requestUri.ToString(), nameof(GetAsync));
|
this.LogInformation(requestUri.ToString(), nameof(GetAsync));
|
||||||
return this.httpClient.GetAsync(requestUri, completionOption, cancellationToken);
|
return this.httpClient.GetAsync(requestUri, completionOption, cancellationToken);
|
||||||
}
|
}
|
||||||
public Task<HttpResponseMessage> GetAsync(Uri requestUri, CancellationToken cancellationToken)
|
public Task<HttpResponseMessage> GetAsync(Uri requestUri, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
this.LogDebug(requestUri.ToString(), nameof(GetAsync));
|
this.LogInformation(requestUri.ToString(), nameof(GetAsync));
|
||||||
return this.httpClient.GetAsync(requestUri, cancellationToken);
|
return this.httpClient.GetAsync(requestUri, cancellationToken);
|
||||||
}
|
}
|
||||||
public Task<byte[]> GetByteArrayAsync(string requestUri)
|
public Task<byte[]> GetByteArrayAsync(string requestUri)
|
||||||
{
|
{
|
||||||
this.LogDebug(requestUri, nameof(GetByteArrayAsync));
|
this.LogInformation(requestUri, nameof(GetByteArrayAsync));
|
||||||
return this.httpClient.GetByteArrayAsync(requestUri);
|
return this.httpClient.GetByteArrayAsync(requestUri);
|
||||||
}
|
}
|
||||||
public Task<byte[]> GetByteArrayAsync(Uri requestUri)
|
public Task<byte[]> GetByteArrayAsync(Uri requestUri)
|
||||||
{
|
{
|
||||||
this.LogDebug(requestUri.ToString(), nameof(GetByteArrayAsync));
|
this.LogInformation(requestUri.ToString(), nameof(GetByteArrayAsync));
|
||||||
return this.httpClient.GetByteArrayAsync(requestUri);
|
return this.httpClient.GetByteArrayAsync(requestUri);
|
||||||
}
|
}
|
||||||
public Task<Stream> GetStreamAsync(string requestUri)
|
public Task<Stream> GetStreamAsync(string requestUri)
|
||||||
{
|
{
|
||||||
this.LogDebug(requestUri, nameof(GetStreamAsync));
|
this.LogInformation(requestUri, nameof(GetStreamAsync));
|
||||||
return this.httpClient.GetStreamAsync(requestUri);
|
return this.httpClient.GetStreamAsync(requestUri);
|
||||||
}
|
}
|
||||||
public Task<Stream> GetStreamAsync(Uri requestUri)
|
public Task<Stream> GetStreamAsync(Uri requestUri)
|
||||||
{
|
{
|
||||||
this.LogDebug(requestUri.ToString(), nameof(GetStreamAsync));
|
this.LogInformation(requestUri.ToString(), nameof(GetStreamAsync));
|
||||||
return this.httpClient.GetStreamAsync(requestUri);
|
return this.httpClient.GetStreamAsync(requestUri);
|
||||||
}
|
}
|
||||||
public Task<string> GetStringAsync(string requestUri)
|
public Task<string> GetStringAsync(string requestUri)
|
||||||
{
|
{
|
||||||
this.LogDebug(requestUri, nameof(GetStringAsync));
|
this.LogInformation(requestUri, nameof(GetStringAsync));
|
||||||
return this.httpClient.GetStringAsync(requestUri);
|
return this.httpClient.GetStringAsync(requestUri);
|
||||||
}
|
}
|
||||||
public Task<string> GetStringAsync(Uri requestUri)
|
public Task<string> GetStringAsync(Uri requestUri)
|
||||||
{
|
{
|
||||||
this.LogDebug(requestUri.ToString(), nameof(GetStringAsync));
|
this.LogInformation(requestUri.ToString(), nameof(GetStringAsync));
|
||||||
return this.httpClient.GetStringAsync(requestUri);
|
return this.httpClient.GetStringAsync(requestUri);
|
||||||
}
|
}
|
||||||
public Task<HttpResponseMessage> PostAsync(string requestUri, HttpContent content)
|
public Task<HttpResponseMessage> PostAsync(string requestUri, HttpContent content)
|
||||||
{
|
{
|
||||||
this.LogDebug(requestUri, nameof(PostAsync));
|
this.LogInformation(requestUri, nameof(PostAsync));
|
||||||
return this.httpClient.PostAsync(requestUri, content);
|
return this.httpClient.PostAsync(requestUri, content);
|
||||||
}
|
}
|
||||||
public Task<HttpResponseMessage> PostAsync(string requestUri, HttpContent content, CancellationToken cancellationToken)
|
public Task<HttpResponseMessage> PostAsync(string requestUri, HttpContent content, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
this.LogDebug(requestUri, nameof(PostAsync));
|
this.LogInformation(requestUri, nameof(PostAsync));
|
||||||
return this.httpClient.PostAsync(requestUri, content, cancellationToken);
|
return this.httpClient.PostAsync(requestUri, content, cancellationToken);
|
||||||
}
|
}
|
||||||
public Task<HttpResponseMessage> PostAsync(Uri requestUri, HttpContent content)
|
public Task<HttpResponseMessage> PostAsync(Uri requestUri, HttpContent content)
|
||||||
{
|
{
|
||||||
this.LogDebug(requestUri.ToString(), nameof(PostAsync));
|
this.LogInformation(requestUri.ToString(), nameof(PostAsync));
|
||||||
return this.httpClient.PostAsync(requestUri, content);
|
return this.httpClient.PostAsync(requestUri, content);
|
||||||
}
|
}
|
||||||
public Task<HttpResponseMessage> PostAsync(Uri requestUri, HttpContent content, CancellationToken cancellationToken)
|
public Task<HttpResponseMessage> PostAsync(Uri requestUri, HttpContent content, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
this.LogDebug(requestUri.ToString(), nameof(PostAsync));
|
this.LogInformation(requestUri.ToString(), nameof(PostAsync));
|
||||||
return this.httpClient.PostAsync(requestUri, content, cancellationToken);
|
return this.httpClient.PostAsync(requestUri, content, cancellationToken);
|
||||||
}
|
}
|
||||||
public Task<HttpResponseMessage> PutAsync(string requestUri, HttpContent content)
|
public Task<HttpResponseMessage> PutAsync(string requestUri, HttpContent content)
|
||||||
{
|
{
|
||||||
this.LogDebug(requestUri, nameof(PutAsync));
|
this.LogInformation(requestUri, nameof(PutAsync));
|
||||||
return this.httpClient.PutAsync(requestUri, content);
|
return this.httpClient.PutAsync(requestUri, content);
|
||||||
}
|
}
|
||||||
public Task<HttpResponseMessage> PutAsync(string requestUri, HttpContent content, CancellationToken cancellationToken)
|
public Task<HttpResponseMessage> PutAsync(string requestUri, HttpContent content, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
this.LogDebug(requestUri, nameof(PutAsync));
|
this.LogInformation(requestUri, nameof(PutAsync));
|
||||||
return this.httpClient.PutAsync(requestUri, content, cancellationToken);
|
return this.httpClient.PutAsync(requestUri, content, cancellationToken);
|
||||||
}
|
}
|
||||||
public Task<HttpResponseMessage> PutAsync(Uri requestUri, HttpContent content)
|
public Task<HttpResponseMessage> PutAsync(Uri requestUri, HttpContent content)
|
||||||
{
|
{
|
||||||
this.LogDebug(requestUri.ToString(), nameof(PutAsync));
|
this.LogInformation(requestUri.ToString(), nameof(PutAsync));
|
||||||
return this.httpClient.PutAsync(requestUri, content);
|
return this.httpClient.PutAsync(requestUri, content);
|
||||||
}
|
}
|
||||||
public Task<HttpResponseMessage> PutAsync(Uri requestUri, HttpContent content, CancellationToken cancellationToken)
|
public Task<HttpResponseMessage> PutAsync(Uri requestUri, HttpContent content, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
this.LogDebug(requestUri.ToString(), nameof(PutAsync));
|
this.LogInformation(requestUri.ToString(), nameof(PutAsync));
|
||||||
return this.httpClient.PutAsync(requestUri, content, cancellationToken);
|
return this.httpClient.PutAsync(requestUri, content, cancellationToken);
|
||||||
}
|
}
|
||||||
public Task<HttpResponseMessage> SendAsync(HttpRequestMessage request)
|
public Task<HttpResponseMessage> SendAsync(HttpRequestMessage request)
|
||||||
{
|
{
|
||||||
this.LogDebug(request.RequestUri.ToString(), nameof(SendAsync));
|
this.LogInformation(request.RequestUri.ToString(), nameof(SendAsync));
|
||||||
return this.httpClient.SendAsync(request);
|
return this.httpClient.SendAsync(request);
|
||||||
}
|
}
|
||||||
public Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, HttpCompletionOption completionOption)
|
public Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, HttpCompletionOption completionOption)
|
||||||
{
|
{
|
||||||
this.LogDebug(request.RequestUri.ToString(), nameof(SendAsync));
|
this.LogInformation(request.RequestUri.ToString(), nameof(SendAsync));
|
||||||
return this.httpClient.SendAsync(request, completionOption);
|
return this.httpClient.SendAsync(request, completionOption);
|
||||||
}
|
}
|
||||||
public Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, HttpCompletionOption completionOption, CancellationToken cancellationToken)
|
public Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, HttpCompletionOption completionOption, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
this.LogDebug(request.RequestUri.ToString(), nameof(SendAsync));
|
this.LogInformation(request.RequestUri.ToString(), nameof(SendAsync));
|
||||||
return this.httpClient.SendAsync(request, completionOption, cancellationToken);
|
return this.httpClient.SendAsync(request, completionOption, cancellationToken);
|
||||||
}
|
}
|
||||||
public Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
|
public Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
this.LogDebug(request.RequestUri.ToString(), nameof(SendAsync));
|
this.LogInformation(request.RequestUri.ToString(), nameof(SendAsync));
|
||||||
return this.httpClient.SendAsync(request, cancellationToken);
|
return this.httpClient.SendAsync(request, cancellationToken);
|
||||||
}
|
}
|
||||||
public void Dispose()
|
public void Dispose()
|
||||||
{
|
{
|
||||||
if (this.disposeInnerClient)
|
this.httpClient.Dispose();
|
||||||
{
|
|
||||||
this.httpClient.Dispose();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void LogDebug(string url, string method)
|
private void LogInformation(string url, string method)
|
||||||
{
|
{
|
||||||
this.eventEmitted?.Invoke(this, new HttpClientEventMessage(this.scope, method, url));
|
this.eventEmitted?.Invoke(this, new HttpClientEventMessage(this.scope, method, url));
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ using System.Extensions;
|
|||||||
|
|
||||||
namespace System.Logging;
|
namespace System.Logging;
|
||||||
|
|
||||||
public readonly struct ScopedLogger<T>
|
public struct ScopedLogger<T>
|
||||||
{
|
{
|
||||||
private readonly ILogger<T> logger;
|
private readonly ILogger<T> logger;
|
||||||
private readonly string scope;
|
private readonly string scope;
|
||||||
|
|||||||
@@ -2,16 +2,15 @@
|
|||||||
|
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<TargetFrameworks>netstandard2.0</TargetFrameworks>
|
<TargetFrameworks>netstandard2.0</TargetFrameworks>
|
||||||
<LangVersion>latest</LangVersion>
|
<LangVersion>latest</LangVersion>
|
||||||
<GeneratePackageOnBuild>true</GeneratePackageOnBuild>
|
<GeneratePackageOnBuild>true</GeneratePackageOnBuild>
|
||||||
<PackageLicenseFile>LICENSE</PackageLicenseFile>
|
<PackageLicenseFile>LICENSE</PackageLicenseFile>
|
||||||
<PackageRequireLicenseAcceptance>true</PackageRequireLicenseAcceptance>
|
<PackageRequireLicenseAcceptance>true</PackageRequireLicenseAcceptance>
|
||||||
<RootNamespace>System</RootNamespace>
|
<RootNamespace>System</RootNamespace>
|
||||||
<Version>1.9</Version>
|
<Version>1.5</Version>
|
||||||
<Authors>Alexandru Macocian</Authors>
|
<Authors>Alexandru Macocian</Authors>
|
||||||
<RepositoryUrl>https://github.com/AlexMacocian/SystemExtensions</RepositoryUrl>
|
<RepositoryUrl>https://github.com/AlexMacocian/SystemExtensions</RepositoryUrl>
|
||||||
<Description>Extensions for the System namespace</Description>
|
<Description>Extensions for the System namespace</Description>
|
||||||
<Nullable>enable</Nullable>
|
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
@@ -23,8 +22,8 @@
|
|||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="8.0.1" />
|
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="6.0.1" />
|
||||||
<PackageReference Include="System.Text.Json" Version="10.0.1" />
|
<PackageReference Include="Newtonsoft.Json" Version="13.0.1" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
</Project>
|
</Project>
|
||||||
|
|||||||
@@ -111,7 +111,7 @@ public class PriorityThreadPool : IDisposable
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
private volatile List<WorkerThread> threadpool;
|
private volatile List<WorkerThread> threadpool;
|
||||||
private volatile PriorityQueue<QueueEntry> tasks;
|
private volatile PriorityQueue<QueueEntry> tasks;
|
||||||
private Thread? observer;
|
private Thread observer;
|
||||||
private CancellationTokenSource observerCancellationTokenSource = new CancellationTokenSource();
|
private CancellationTokenSource observerCancellationTokenSource = new CancellationTokenSource();
|
||||||
private readonly object tasksLock = new object();
|
private readonly object tasksLock = new object();
|
||||||
private struct WorkerThread
|
private struct WorkerThread
|
||||||
@@ -307,7 +307,7 @@ public class PriorityThreadPool : IDisposable
|
|||||||
//Finally, release the lock and invoke the task.
|
//Finally, release the lock and invoke the task.
|
||||||
|
|
||||||
thisWorkerThread.Working = true;
|
thisWorkerThread.Working = true;
|
||||||
QueueEntry task = default!;
|
QueueEntry task = null;
|
||||||
while (!Monitor.TryEnter(this.tasksLock))
|
while (!Monitor.TryEnter(this.tasksLock))
|
||||||
{
|
{
|
||||||
;
|
;
|
||||||
@@ -451,7 +451,7 @@ public class PriorityThreadPool : IDisposable
|
|||||||
/// Find a thread with Lowest or BelowNormal priority.
|
/// Find a thread with Lowest or BelowNormal priority.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <returns>Thread with low priority.</returns>
|
/// <returns>Thread with low priority.</returns>
|
||||||
private Thread? FindThreadWithLowPriority()
|
private Thread FindThreadWithLowPriority()
|
||||||
{
|
{
|
||||||
foreach (var t in this.threadpool)
|
foreach (var t in this.threadpool)
|
||||||
{
|
{
|
||||||
@@ -461,13 +461,13 @@ public class PriorityThreadPool : IDisposable
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return default;
|
return null;
|
||||||
}
|
}
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Find a thread with BelowNormal or Normal priority.
|
/// Find a thread with BelowNormal or Normal priority.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <returns>Thread with BelowNormal or Normal priority.</returns>
|
/// <returns>Thread with BelowNormal or Normal priority.</returns>
|
||||||
private Thread? FindThreadWithAcceptablePriority()
|
private Thread FindThreadWithAcceptablePriority()
|
||||||
{
|
{
|
||||||
foreach (var t in this.threadpool)
|
foreach (var t in this.threadpool)
|
||||||
{
|
{
|
||||||
@@ -477,7 +477,7 @@ public class PriorityThreadPool : IDisposable
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return default;
|
return null;
|
||||||
}
|
}
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Downgrades the priority of a thread one level.
|
/// Downgrades the priority of a thread one level.
|
||||||
@@ -560,9 +560,9 @@ public class PriorityThreadPool : IDisposable
|
|||||||
this.tasks.Clear();
|
this.tasks.Clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
this.threadpool = default!;
|
this.threadpool = null;
|
||||||
this.tasks = default!;
|
this.tasks = null;
|
||||||
this.observer = default;
|
this.observer = null;
|
||||||
this.disposedValue = true;
|
this.disposedValue = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,30 +0,0 @@
|
|||||||
using System.Extensions;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
|
|
||||||
namespace System.Threading;
|
|
||||||
public readonly struct SemaphoreSlimContext : IDisposable
|
|
||||||
{
|
|
||||||
private readonly SemaphoreSlim semaphore;
|
|
||||||
|
|
||||||
private SemaphoreSlimContext(SemaphoreSlim semaphoreSlim)
|
|
||||||
{
|
|
||||||
this.semaphore = semaphoreSlim.ThrowIfNull(nameof(semaphoreSlim));
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Dispose()
|
|
||||||
{
|
|
||||||
this.semaphore.Release();
|
|
||||||
}
|
|
||||||
|
|
||||||
public static async Task<SemaphoreSlimContext> Create(SemaphoreSlim semaphore)
|
|
||||||
{
|
|
||||||
await semaphore.ThrowIfNull(nameof(semaphore)).WaitAsync();
|
|
||||||
return new SemaphoreSlimContext(semaphore);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static async Task<SemaphoreSlimContext> Create(SemaphoreSlim semaphore, CancellationToken cancellationToken)
|
|
||||||
{
|
|
||||||
await semaphore.ThrowIfNull(nameof(semaphore)).WaitAsync(cancellationToken);
|
|
||||||
return new SemaphoreSlimContext(semaphore);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,98 +0,0 @@
|
|||||||
using Microsoft.Extensions.Logging;
|
|
||||||
using System.Extensions;
|
|
||||||
using System.Threading;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
|
|
||||||
namespace System.Net.WebSockets;
|
|
||||||
|
|
||||||
// TODO: Cannot properly test because ClientWebSocket is sealed
|
|
||||||
public sealed class ClientWebSocket<TScope> : IClientWebSocket<TScope>, IDisposable
|
|
||||||
{
|
|
||||||
private readonly ILogger<TScope>? logger;
|
|
||||||
private ClientWebSocket internalWebSocket = new();
|
|
||||||
|
|
||||||
public ClientWebSocketOptions Options => this.internalWebSocket.Options;
|
|
||||||
|
|
||||||
public WebSocketCloseStatus? CloseStatus => this.internalWebSocket.CloseStatus;
|
|
||||||
|
|
||||||
public string CloseStatusDescription => this.internalWebSocket.CloseStatusDescription;
|
|
||||||
|
|
||||||
public string SubProtocol => this.internalWebSocket.SubProtocol;
|
|
||||||
|
|
||||||
public WebSocketState State => this.internalWebSocket.State;
|
|
||||||
|
|
||||||
public ClientWebSocket()
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
public ClientWebSocket(ILogger<TScope> logger)
|
|
||||||
{
|
|
||||||
this.logger = logger.ThrowIfNull(nameof(logger));
|
|
||||||
}
|
|
||||||
|
|
||||||
public ClientWebSocket(ClientWebSocket clientWebSocket, ILogger<TScope> logger)
|
|
||||||
{
|
|
||||||
this.internalWebSocket = clientWebSocket.ThrowIfNull(nameof(clientWebSocket));
|
|
||||||
this.logger = logger.ThrowIfNull(nameof(logger));
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Dispose()
|
|
||||||
{
|
|
||||||
var scopedLogger = this.logger?.CreateScopedLogger(nameof(Dispose), string.Empty);
|
|
||||||
scopedLogger?.LogDebug($"Disposing websocket");
|
|
||||||
this.internalWebSocket?.Dispose();
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Abort()
|
|
||||||
{
|
|
||||||
var scopedLogger = this.logger?.CreateScopedLogger(nameof(Abort), string.Empty);
|
|
||||||
scopedLogger?.LogDebug($"Aborting websocket");
|
|
||||||
this.internalWebSocket.Abort();
|
|
||||||
}
|
|
||||||
|
|
||||||
public Task CloseAsync(WebSocketCloseStatus closeStatus, string statusDescription, CancellationToken cancellationToken)
|
|
||||||
{
|
|
||||||
var scopedLogger = this.logger?.CreateScopedLogger(nameof(CloseAsync), string.Empty);
|
|
||||||
scopedLogger?.LogDebug($"Closing websocket. Status [{closeStatus}]. Status Description [{statusDescription}]");
|
|
||||||
return this.internalWebSocket.CloseAsync(closeStatus, statusDescription, cancellationToken);
|
|
||||||
}
|
|
||||||
|
|
||||||
public Task CloseOutputAsync(WebSocketCloseStatus closeStatus, string statusDescription, CancellationToken cancellationToken)
|
|
||||||
{
|
|
||||||
var scopedLogger = this.logger?.CreateScopedLogger(nameof(CloseOutputAsync), string.Empty);
|
|
||||||
scopedLogger?.LogDebug($"Closing output. Status [{closeStatus}]. Status Description [{statusDescription}]");
|
|
||||||
return this.internalWebSocket.CloseOutputAsync(closeStatus, statusDescription, cancellationToken);
|
|
||||||
}
|
|
||||||
|
|
||||||
public Task ConnectAsync(Uri uri, CancellationToken cancellationToken)
|
|
||||||
{
|
|
||||||
var scopedLogger = this.logger?.CreateScopedLogger(nameof(ConnectAsync), string.Empty);
|
|
||||||
scopedLogger?.LogDebug($"Connecting to {uri}");
|
|
||||||
return this.internalWebSocket.ConnectAsync(uri, cancellationToken);
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task<WebSocketReceiveResult> ReceiveAsync(ArraySegment<byte> buffer, CancellationToken cancellationToken)
|
|
||||||
{
|
|
||||||
var scopedLogger = this.logger?.CreateScopedLogger(nameof(ConnectAsync), string.Empty);
|
|
||||||
scopedLogger?.LogDebug($"Attempting to receive bytes");
|
|
||||||
var result = await this.internalWebSocket.ReceiveAsync(buffer, cancellationToken);
|
|
||||||
scopedLogger?.LogDebug($"Received message [{result.MessageType}]");
|
|
||||||
scopedLogger?.LogDebug($"Type: {result.MessageType}\nCount: {result.Count}\nEndOfMessage: {result.EndOfMessage}\nCloseStatus: {result.CloseStatus}\nCloseStatusDescription: {result.CloseStatusDescription}");
|
|
||||||
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Task SendAsync(ArraySegment<byte> buffer, WebSocketMessageType messageType, bool endOfMessage, CancellationToken cancellationToken)
|
|
||||||
{
|
|
||||||
var scopedLogger = this.logger?.CreateScopedLogger(nameof(SendAsync), string.Empty);
|
|
||||||
scopedLogger?.LogDebug($"Sending bytes");
|
|
||||||
scopedLogger?.LogDebug($"Type: {messageType}\nCount: {buffer.Count}\nEndOfMessage: {endOfMessage}");
|
|
||||||
return this.internalWebSocket.SendAsync(buffer, messageType, endOfMessage, cancellationToken);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void RefreshSocket(ClientWebSocket? clientWebSocket = null)
|
|
||||||
{
|
|
||||||
this.internalWebSocket?.Dispose();
|
|
||||||
this.internalWebSocket = clientWebSocket ?? new ClientWebSocket();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,33 +0,0 @@
|
|||||||
using System.Threading.Tasks;
|
|
||||||
using System.Threading;
|
|
||||||
|
|
||||||
namespace System.Net.WebSockets;
|
|
||||||
|
|
||||||
public interface IClientWebSocket<TScope>
|
|
||||||
{
|
|
||||||
WebSocketCloseStatus? CloseStatus { get; }
|
|
||||||
|
|
||||||
string CloseStatusDescription { get; }
|
|
||||||
|
|
||||||
ClientWebSocketOptions Options { get; }
|
|
||||||
|
|
||||||
WebSocketState State { get; }
|
|
||||||
|
|
||||||
string SubProtocol { get; }
|
|
||||||
|
|
||||||
void Abort();
|
|
||||||
|
|
||||||
Task CloseAsync(WebSocketCloseStatus closeStatus, string statusDescription, CancellationToken cancellationToken);
|
|
||||||
|
|
||||||
Task CloseOutputAsync(WebSocketCloseStatus closeStatus, string statusDescription, CancellationToken cancellationToken);
|
|
||||||
|
|
||||||
Task ConnectAsync(Uri uri, CancellationToken cancellationToken);
|
|
||||||
|
|
||||||
void Dispose();
|
|
||||||
|
|
||||||
Task<WebSocketReceiveResult> ReceiveAsync(ArraySegment<byte> buffer, CancellationToken cancellationToken);
|
|
||||||
|
|
||||||
Task SendAsync(ArraySegment<byte> buffer, WebSocketMessageType messageType, bool endOfMessage, CancellationToken cancellationToken);
|
|
||||||
|
|
||||||
void RefreshSocket(ClientWebSocket? clientWebSocket = default);
|
|
||||||
}
|
|
||||||
@@ -124,4 +124,36 @@ public class AVLTreeTests
|
|||||||
|
|
||||||
_ = this.avlTree.ToArray();
|
_ = this.avlTree.ToArray();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[TestMethod()]
|
||||||
|
public void Serialize()
|
||||||
|
{
|
||||||
|
var avlTree2 = new AVLTree<int>();
|
||||||
|
for (var i = 0; i < 100; i++)
|
||||||
|
{
|
||||||
|
this.avlTree.Add(i);
|
||||||
|
}
|
||||||
|
|
||||||
|
var serializer = new BinaryFormatter();
|
||||||
|
using (var stream = new MemoryStream())
|
||||||
|
{
|
||||||
|
serializer.Serialize(stream, this.avlTree);
|
||||||
|
stream.Position = 0;
|
||||||
|
avlTree2 = (AVLTree<int>)serializer.Deserialize(stream);
|
||||||
|
}
|
||||||
|
|
||||||
|
var avlTreeEnum = this.avlTree.GetEnumerator();
|
||||||
|
var avlTree2Enum = avlTree2.GetEnumerator();
|
||||||
|
|
||||||
|
for(var i = 0; i < 100; i++)
|
||||||
|
{
|
||||||
|
if(avlTreeEnum.Current != avlTree2Enum.Current)
|
||||||
|
{
|
||||||
|
Assert.Fail();
|
||||||
|
}
|
||||||
|
|
||||||
|
avlTreeEnum.MoveNext();
|
||||||
|
avlTree2Enum.MoveNext();
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user