diff --git a/AMWD.Common.AspNetCore/BasicAuthentication/BasicAuthenticationMiddleware.cs b/AMWD.Common.AspNetCore/BasicAuthentication/BasicAuthenticationMiddleware.cs index 14e9b37..b96c046 100644 --- a/AMWD.Common.AspNetCore/BasicAuthentication/BasicAuthenticationMiddleware.cs +++ b/AMWD.Common.AspNetCore/BasicAuthentication/BasicAuthenticationMiddleware.cs @@ -12,19 +12,16 @@ namespace AMWD.Common.AspNetCore.BasicAuthentication public class BasicAuthenticationMiddleware { private readonly RequestDelegate next; - private readonly string realm; private readonly IBasicAuthenticationValidator validator; /// /// Initializes a new instance of the class. /// /// The following delegate in the process chain. - /// The realm to display when requesting for credentials. /// A basic authentication validator. - public BasicAuthenticationMiddleware(RequestDelegate next, string realm, IBasicAuthenticationValidator validator) + public BasicAuthenticationMiddleware(RequestDelegate next, IBasicAuthenticationValidator validator) { this.next = next; - this.realm = realm; this.validator = validator; } @@ -53,10 +50,8 @@ namespace AMWD.Common.AspNetCore.BasicAuthentication } httpContext.Response.Headers["WWW-Authenticate"] = "Basic"; - if (!string.IsNullOrWhiteSpace(realm)) - { - httpContext.Response.Headers["WWW-Authenticate"] += $" realm=\"{realm}\""; - } + if (!string.IsNullOrWhiteSpace(validator.Realm)) + httpContext.Response.Headers["WWW-Authenticate"] += $" realm=\"{validator.Realm}\""; httpContext.Response.StatusCode = StatusCodes.Status401Unauthorized; } diff --git a/AMWD.Common.AspNetCore/BasicAuthentication/IBasicAuthenticationValidator.cs b/AMWD.Common.AspNetCore/BasicAuthentication/IBasicAuthenticationValidator.cs index 84972f7..d2d865b 100644 --- a/AMWD.Common.AspNetCore/BasicAuthentication/IBasicAuthenticationValidator.cs +++ b/AMWD.Common.AspNetCore/BasicAuthentication/IBasicAuthenticationValidator.cs @@ -9,6 +9,11 @@ namespace AMWD.Common.AspNetCore.BasicAuthentication /// public interface IBasicAuthenticationValidator { + /// + /// Gets or sets the realm to use when requesting authentication. + /// + string Realm { get; set; } + /// /// Validates a username and password for Basic Authentication. /// diff --git a/AMWD.Common.EntityFrameworkCore/Extensions/DatabaseFacadeExtensions.cs b/AMWD.Common.EntityFrameworkCore/Extensions/DatabaseFacadeExtensions.cs index 82a5c29..7dd8dab 100644 --- a/AMWD.Common.EntityFrameworkCore/Extensions/DatabaseFacadeExtensions.cs +++ b/AMWD.Common.EntityFrameworkCore/Extensions/DatabaseFacadeExtensions.cs @@ -66,19 +66,24 @@ namespace Microsoft.EntityFrameworkCore return DatabaseProvider.SQLite; if (provider.Contains("sqlclient", StringComparison.OrdinalIgnoreCase)) return DatabaseProvider.SQLServer; + if (provider.Contains("inmemory", StringComparison.OrdinalIgnoreCase)) + return DatabaseProvider.InMemory; throw new DatabaseProviderException($"The database provider '{provider}' is unknown"); } private static async Task CreateMigrationsTable(this DbConnection connection, DatabaseMigrationOptions options, CancellationToken cancellationToken) { + if (connection.GetProviderType() == DatabaseProvider.InMemory) + return true; + try { using var command = connection.CreateCommand(); -#pragma warning disable CS8524 // missing default case +#pragma warning disable CS8509 // ignore missing cases command.CommandText = connection.GetProviderType() switch -#pragma warning restore CS8524 // missing default case +#pragma warning restore CS8509 // ignore missing cases { DatabaseProvider.MySQL => $@"CREATE TABLE IF NOT EXISTS `{options.MigrationsTableName}` ( `id` INT NOT NULL AUTO_INCREMENT, @@ -135,6 +140,9 @@ END;" private static async Task Migrate(this DbConnection connection, DatabaseMigrationOptions options, CancellationToken cancellationToken) { + if (connection.GetProviderType() == DatabaseProvider.InMemory) + return true; + try { List availableMigrationFiles; @@ -279,7 +287,8 @@ END;" Oracle = 2, PostgreSQL = 3, SQLite = 4, - SQLServer = 5 + SQLServer = 5, + InMemory = 6, } } } diff --git a/AMWD.Common.EntityFrameworkCore/Extensions/DbContextOptionsBuilderExtensions.cs b/AMWD.Common.EntityFrameworkCore/Extensions/DbContextOptionsBuilderExtensions.cs index 51646f9..edf410f 100644 --- a/AMWD.Common.EntityFrameworkCore/Extensions/DbContextOptionsBuilderExtensions.cs +++ b/AMWD.Common.EntityFrameworkCore/Extensions/DbContextOptionsBuilderExtensions.cs @@ -79,6 +79,10 @@ namespace Microsoft.EntityFrameworkCore case "mssql": methodInfo = extensionType.GetMethod("UseSqlServer", new Type[] { typeof(DbContextOptionsBuilder), typeof(string), actionType }); break; + case "memory": + case "inmemory": + methodInfo = extensionType.GetMethod("UseInMemoryDatabase", new Type[] { typeof(DbContextOptionsBuilder), typeof(string), actionType }); + break; default: throw new DatabaseProviderException($"Unknown database provider: {provider}"); } @@ -122,6 +126,10 @@ namespace Microsoft.EntityFrameworkCore case "mssql": builderType = Type.GetType("Microsoft.EntityFrameworkCore.Infrastructure.SqlServerDbContextOptionsBuilder, Microsoft.EntityFrameworkCore.SqlServer"); break; + case "memory": + case "inmemory": + builderType = Type.GetType("Microsoft.EntityFrameworkCore.Infrastructure.InMemoryDbContextOptionsBuilder, Microsoft.EntityFrameworkCore.InMemory"); + break; default: throw new ArgumentException($"Unknown database provider: {provider}"); } @@ -155,6 +163,10 @@ namespace Microsoft.EntityFrameworkCore case "mssql": extensionType = Type.GetType("Microsoft.EntityFrameworkCore.SqlServerDbContextOptionsExtensions, Microsoft.EntityFrameworkCore.SqlServer"); break; + case "memory": + case "inmemory": + extensionType = Type.GetType("Microsoft.EntityFrameworkCore.InMemoryDbContextOptionsExtensions, Microsoft.EntityFrameworkCore.InMemory"); + break; default: throw new ArgumentException($"Unknown database provider: {provider}"); } @@ -219,6 +231,10 @@ namespace Microsoft.EntityFrameworkCore } cs.Add("Connect Timeout=15"); break; + case "memory": + case "inmemory": + cs.Add(configuration.GetValue("Name", provider)); + break; default: throw new DatabaseProviderException($"Unknown database provider: {provider}"); } diff --git a/AMWD.Common.Moq/AMWD.Common.Moq.csproj b/AMWD.Common.Moq/AMWD.Common.Moq.csproj new file mode 100644 index 0000000..b0afde3 --- /dev/null +++ b/AMWD.Common.Moq/AMWD.Common.Moq.csproj @@ -0,0 +1,46 @@ + + + + netstandard2.0 + 10.0 + + AMWD.Common.Moq + AMWD.Common.Moq + {semvertag:master:+chash}{!:-dirty} + + true + false + true + + true + true + snupkg + + AMWD.Common.Moq + icon.png + https://wiki.am-wd.de/libs/common + + git + https://git.am-wd.de/AM.WD/common.git + + AM.WD Common Library for Moq + Library with classes and extensions used frequently on AM.WD projects. + AM.WD + Andreas Müller + © {copyright:2020-} AM.WD + MIT + + + + + + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + diff --git a/AMWD.Common.Moq/HttpMessageHandlerMoq.cs b/AMWD.Common.Moq/HttpMessageHandlerMoq.cs new file mode 100644 index 0000000..d5b18dd --- /dev/null +++ b/AMWD.Common.Moq/HttpMessageHandlerMoq.cs @@ -0,0 +1,130 @@ +using System; +using System.Collections.Generic; +using System.Net; +using System.Net.Http; +using System.Net.Http.Headers; +using System.Threading; +using System.Threading.Tasks; +using Moq; +using Moq.Protected; + +namespace AMWD.Common.Moq +{ + /// + /// Wrapps the including the setup. + /// + public class HttpMessageHandlerMoq + { + /// + /// Initializes a new instance of the class. + /// + public HttpMessageHandlerMoq() + { + Response = new() { StatusCode = HttpStatusCode.OK }; + Callbacks = new(); + + Mock = new(); + Mock.Protected() + .Setup>("SendAsync", ItExpr.IsAny(), ItExpr.IsAny()) + .Callback(async (req, _) => + { + var callback = new HttpMessageRequestCallback + { + Headers = req.Headers, + Method = req.Method, + Properties = req.Properties, + RequestUri = req.RequestUri, + Version = req.Version + }; + + if (req.Content != null) + { + callback.ContentBytes = await req.Content.ReadAsByteArrayAsync(); + callback.ContentString = await req.Content.ReadAsStringAsync(); + } + + Callbacks.Add(callback); + }) + .ReturnsAsync(Response); + } + + /// + /// Gets the mocked . + /// + public Mock Mock { get; } + + /// + /// Gets the placed request. + /// + public List Callbacks { get; private set; } + + /// + /// Gets the HTTP response, that should be "sent". + /// + public HttpResponseMessage Response { get; private set; } + + /// + /// Disposes and resets the and . + /// + public void Reset() + { + Response.Dispose(); + Response = new() { StatusCode = HttpStatusCode.OK }; + + Callbacks.Clear(); + } + + /// + /// Verifies the number of calls to the HTTP request. + /// + /// + public void Verify(Times times) + => Mock.Protected().Verify("SendAsync", times, ItExpr.IsAny(), ItExpr.IsAny()); + + /// + /// Represents the placed HTTP request. + /// + public class HttpMessageRequestCallback + { + /// + /// Gets the contents of the HTTP message. + /// + public byte[] ContentBytes { get; internal set; } + + /// + /// Gets the contents of the HTTP message. + /// + public string ContentString { get; internal set; } + + /// + /// Gets the collection of HTTP request headers. + /// + public HttpRequestHeaders Headers { get; internal set; } + + /// + /// Gets the HTTP method used by the HTTP request message. + /// + public HttpMethod Method { get; internal set; } + + /// + /// Gets of properties for the HTTP request. + /// + public IDictionary Properties { get; internal set; } + + /// + /// Gets the used for the HTTP request. + /// + public Uri RequestUri { get; internal set; } + + /// + /// Gets the string representation. + /// + public string RequestUrl => RequestUri?.ToString(); + + /// + /// Gets the HTTP message version. + /// + public Version Version { get; internal set; } + } + } +} diff --git a/AMWD.Common/Extensions/StringExtensions.cs b/AMWD.Common/Extensions/StringExtensions.cs index df766bd..2b4f971 100644 --- a/AMWD.Common/Extensions/StringExtensions.cs +++ b/AMWD.Common/Extensions/StringExtensions.cs @@ -5,6 +5,7 @@ using System.Net; using System.Net.Mail; using System.Text; using System.Text.RegularExpressions; +using System.Threading.Tasks; using DNS.Client; using DNS.Protocol; @@ -170,18 +171,17 @@ namespace System var mailAddress = new MailAddress(email); bool isValid = mailAddress.Address == email; - if (isValid && nameservers != null) + if (isValid && nameservers?.Any() == true) { bool exists = false; foreach (var nameserver in nameservers) { - var clientRequest = new ClientRequest(nameserver) { RecursionDesired = true }; - clientRequest.Questions.Add(new Question(Domain.FromString(mailAddress.Host), RecordType.MX)); + var client = new DnsClient(nameserver); + var waitTask = Task.Run(async () => await client.Resolve(mailAddress.Host, RecordType.MX)); + waitTask.Wait(); - var response = clientRequest.Resolve() - .ConfigureAwait(false) - .GetAwaiter() - .GetResult(); + var response = waitTask.Result; + waitTask.Dispose(); if (response.ResponseCode != ResponseCode.NoError) continue; diff --git a/CHANGELOG.md b/CHANGELOG.md index 2c1203a..9980b3a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,10 +6,12 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased](https://git.am-wd.de/AM.WD/common/compare/v1.3.0...master) - 0000-00-00 ### Added - +- New NuGet package `AMWD.Common.Moq` with `HttpRequestHandlerMoq` class +- Supporting `InMemory` Database as provider ### Changed - Enhanced Stopwatch/Timer delta due to unsharp resolution using timers +- `IBasicAuthenticationValidator` now requires also the realm (resolving a string on DI is bad style) ## [v1.3.0](https://git.am-wd.de/AM.WD/common/compare/v1.2.0...v1.3.0) - 2021-12-21 diff --git a/Common.sln b/Common.sln index 3a5745a..3c4d722 100644 --- a/Common.sln +++ b/Common.sln @@ -26,6 +26,8 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{F2C7556A-99E EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "test", "test", "{E5DF156A-6C8B-4004-BA4C-A8DDE6FD3ECD}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AMWD.Common.Moq", "AMWD.Common.Moq\AMWD.Common.Moq.csproj", "{6EBA2792-0B66-4C90-89A1-4E1D26D16443}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -48,6 +50,10 @@ Global {086E3C11-454A-4C8F-AEAA-215BAE9C443F}.Debug|Any CPU.Build.0 = Debug|Any CPU {086E3C11-454A-4C8F-AEAA-215BAE9C443F}.Release|Any CPU.ActiveCfg = Release|Any CPU {086E3C11-454A-4C8F-AEAA-215BAE9C443F}.Release|Any CPU.Build.0 = Release|Any CPU + {6EBA2792-0B66-4C90-89A1-4E1D26D16443}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {6EBA2792-0B66-4C90-89A1-4E1D26D16443}.Debug|Any CPU.Build.0 = Debug|Any CPU + {6EBA2792-0B66-4C90-89A1-4E1D26D16443}.Release|Any CPU.ActiveCfg = Release|Any CPU + {6EBA2792-0B66-4C90-89A1-4E1D26D16443}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -57,6 +63,7 @@ Global {725F40C9-8172-487F-B3D0-D7E38B4DB197} = {F2C7556A-99EB-43EB-8954-56A24AFE928F} {7091CECF-C981-4FB9-9CC6-91C4E65A6356} = {F2C7556A-99EB-43EB-8954-56A24AFE928F} {086E3C11-454A-4C8F-AEAA-215BAE9C443F} = {E5DF156A-6C8B-4004-BA4C-A8DDE6FD3ECD} + {6EBA2792-0B66-4C90-89A1-4E1D26D16443} = {F2C7556A-99EB-43EB-8954-56A24AFE928F} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {961E8DF8-DDF5-4D10-A510-CE409E9962AC} diff --git a/build.cmd b/build.cmd index 38adee8..d2156f3 100644 --- a/build.cmd +++ b/build.cmd @@ -40,3 +40,14 @@ rmdir /S /Q bin dotnet build -c %Configuration% --nologo --no-incremental move bin\%Configuration%\*.nupkg ..\artifacts move bin\%Configuration%\*.snupkg ..\artifacts + + + +cd "%~dp0" +cd "AMWD.Common.Moq" +powershell write-host -fore Blue Building AMWD.Common.Moq + +rmdir /S /Q bin +dotnet build -c %Configuration% --nologo --no-incremental +move bin\%Configuration%\*.nupkg ..\artifacts +move bin\%Configuration%\*.snupkg ..\artifacts \ No newline at end of file diff --git a/build.sh b/build.sh index bd0b7ee..cddfbde 100644 --- a/build.sh +++ b/build.sh @@ -28,3 +28,10 @@ dotnet build -c ${CONFIGURATION} --nologo --no-incremental mv bin/${CONFIGURATION}/*.nupkg ../artifacts mv bin/${CONFIGURATION}/*.snupkg ../artifacts popd + +pushd AMWD.Common.Moq +rm -rf bin +dotnet build -c ${CONFIGURATION} --nologo --no-incremental +mv bin/${CONFIGURATION}/*.nupkg ../artifacts +mv bin/${CONFIGURATION}/*.snupkg ../artifacts +popd