Added White-/Blacklist Attributes, DateOnly/TimeOnly converters, TcpClientMoq
This commit is contained in:
@@ -38,11 +38,11 @@
|
|||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<FrameworkReference Include="Microsoft.AspNetCore.App" />
|
<FrameworkReference Include="Microsoft.AspNetCore.App" />
|
||||||
<PackageReference Include="Newtonsoft.Json" Version="10.0.1" />
|
<PackageReference Include="Newtonsoft.Json" Version="10.0.1" />
|
||||||
<PackageReference Include="Unclassified.DeepConvert" Version="1.3.0" />
|
<PackageReference Include="Unclassified.DeepConvert" Version="1.4.0" />
|
||||||
<PackageReference Include="Unclassified.NetRevisionTask" Version="0.4.2">
|
<PackageReference Include="Unclassified.NetRevisionTask" Version="0.4.3">
|
||||||
<PrivateAssets>all</PrivateAssets>
|
<PrivateAssets>all</PrivateAssets>
|
||||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||||
</PackageReference>
|
</PackageReference>
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
</Project>
|
</Project>
|
||||||
103
AMWD.Common.AspNetCore/Attributes/IPBlacklistAttribute.cs
Normal file
103
AMWD.Common.AspNetCore/Attributes/IPBlacklistAttribute.cs
Normal file
@@ -0,0 +1,103 @@
|
|||||||
|
using System.Linq;
|
||||||
|
using System.Net;
|
||||||
|
using Microsoft.AspNetCore.Http;
|
||||||
|
using Microsoft.AspNetCore.HttpOverrides;
|
||||||
|
using Microsoft.AspNetCore.Mvc;
|
||||||
|
using Microsoft.AspNetCore.Mvc.Filters;
|
||||||
|
using Microsoft.Extensions.Configuration;
|
||||||
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
|
|
||||||
|
namespace AMWD.Common.AspNetCore.Attributes
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Implements an IP filter. The defined addresses are blocked.
|
||||||
|
/// </summary>
|
||||||
|
public class IPBlacklistAttribute : ActionFilterAttribute
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets a value indicating whether local (localhost) access is blocked (Default: false).
|
||||||
|
/// </summary>
|
||||||
|
public bool RestrictLocalAccess { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets a configuration key where the blocked IP addresses are defined.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// JSON configuration example:<br/>
|
||||||
|
/// {<br/>
|
||||||
|
/// "ConfigurationKey": [<br/>
|
||||||
|
/// "10.0.0.0/8",<br/>
|
||||||
|
/// "172.16.0.0/12",<br/>
|
||||||
|
/// "fd00:123:abc::13"<br/>
|
||||||
|
/// ]<br/>
|
||||||
|
/// }
|
||||||
|
/// </remarks>
|
||||||
|
public string ConfigurationKey { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets a comma separated list of blocked IP addresses.
|
||||||
|
/// </summary>
|
||||||
|
public string RestrictedIpAddresses { get; set; }
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public override void OnActionExecuting(ActionExecutingContext context)
|
||||||
|
{
|
||||||
|
base.OnActionExecuting(context);
|
||||||
|
|
||||||
|
if (!RestrictLocalAccess && context.HttpContext.IsLocalRequest())
|
||||||
|
return;
|
||||||
|
|
||||||
|
var remoteIpAddress = context.HttpContext.GetRemoteIpAddress();
|
||||||
|
if (!string.IsNullOrWhiteSpace(RestrictedIpAddresses))
|
||||||
|
{
|
||||||
|
string[] ipAddresses = RestrictedIpAddresses.Split(',');
|
||||||
|
foreach (string ipAddress in ipAddresses)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrWhiteSpace(ipAddress))
|
||||||
|
continue;
|
||||||
|
|
||||||
|
if (MatchesIpAddress(ipAddress, remoteIpAddress))
|
||||||
|
{
|
||||||
|
context.Result = new ForbidResult();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var configuration = context.HttpContext.RequestServices.GetService<IConfiguration>();
|
||||||
|
if (!string.IsNullOrWhiteSpace(ConfigurationKey) && configuration != null)
|
||||||
|
{
|
||||||
|
var section = configuration.GetSection(ConfigurationKey);
|
||||||
|
if (!section.Exists())
|
||||||
|
return;
|
||||||
|
|
||||||
|
foreach (var child in section.GetChildren())
|
||||||
|
{
|
||||||
|
if (string.IsNullOrWhiteSpace(child.Value))
|
||||||
|
continue;
|
||||||
|
|
||||||
|
if (MatchesIpAddress(child.Value, remoteIpAddress))
|
||||||
|
{
|
||||||
|
context.Result = new ForbidResult();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static bool MatchesIpAddress(string configIpAddress, IPAddress remoteIpAddress)
|
||||||
|
{
|
||||||
|
if (configIpAddress.Contains('/'))
|
||||||
|
{
|
||||||
|
string[] ipNetworkParts = configIpAddress.Split('/');
|
||||||
|
var ip = IPAddress.Parse(ipNetworkParts.First());
|
||||||
|
int prefix = int.Parse(ipNetworkParts.Last());
|
||||||
|
|
||||||
|
var net = new IPNetwork(ip, prefix);
|
||||||
|
return net.Contains(remoteIpAddress);
|
||||||
|
}
|
||||||
|
|
||||||
|
return IPAddress.Parse(configIpAddress).Equals(remoteIpAddress);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
102
AMWD.Common.AspNetCore/Attributes/IPWhitelistAttribute.cs
Normal file
102
AMWD.Common.AspNetCore/Attributes/IPWhitelistAttribute.cs
Normal file
@@ -0,0 +1,102 @@
|
|||||||
|
using System.Linq;
|
||||||
|
using System.Net;
|
||||||
|
using Microsoft.AspNetCore.Http;
|
||||||
|
using Microsoft.AspNetCore.HttpOverrides;
|
||||||
|
using Microsoft.AspNetCore.Mvc;
|
||||||
|
using Microsoft.AspNetCore.Mvc.Filters;
|
||||||
|
using Microsoft.Extensions.Configuration;
|
||||||
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
|
|
||||||
|
namespace AMWD.Common.AspNetCore.Attributes
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Implements an IP filter. Only defined addresses are allowed to access.
|
||||||
|
/// </summary>
|
||||||
|
public class IPWhitelistAttribute : ActionFilterAttribute
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets a value indicating whether local (localhost) access is granted (Default: true).
|
||||||
|
/// </summary>
|
||||||
|
public bool AllowLocalAccess { get; set; } = true;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets a configuration key where the allowed IP addresses are allowed.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// JSON configuration example:<br/>
|
||||||
|
/// {<br/>
|
||||||
|
/// "ConfigurationKey": [<br/>
|
||||||
|
/// "10.0.0.0/8",<br/>
|
||||||
|
/// "172.16.0.0/12",<br/>
|
||||||
|
/// "fd00:123:abc::13"<br/>
|
||||||
|
/// ]<br/>
|
||||||
|
/// }
|
||||||
|
/// </remarks>
|
||||||
|
public string ConfigurationKey { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets a comma separated list of allowed IP addresses.
|
||||||
|
/// </summary>
|
||||||
|
public string AllowedIpAddresses { get; set; }
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public override void OnActionExecuting(ActionExecutingContext context)
|
||||||
|
{
|
||||||
|
base.OnActionExecuting(context);
|
||||||
|
|
||||||
|
if (AllowLocalAccess && context.HttpContext.IsLocalRequest())
|
||||||
|
return;
|
||||||
|
|
||||||
|
var remoteIpAddress = context.HttpContext.GetRemoteIpAddress();
|
||||||
|
if (!string.IsNullOrWhiteSpace(AllowedIpAddresses))
|
||||||
|
{
|
||||||
|
string[] ipAddresses = AllowedIpAddresses.Split(',');
|
||||||
|
foreach (string ipAddress in ipAddresses)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrWhiteSpace(ipAddress))
|
||||||
|
continue;
|
||||||
|
|
||||||
|
if (MatchesIpAddress(ipAddress, remoteIpAddress))
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var configuration = context.HttpContext.RequestServices.GetService<IConfiguration>();
|
||||||
|
if (!string.IsNullOrWhiteSpace(ConfigurationKey) && configuration != null)
|
||||||
|
{
|
||||||
|
var section = configuration.GetSection(ConfigurationKey);
|
||||||
|
if (!section.Exists())
|
||||||
|
{
|
||||||
|
context.Result = new ForbidResult();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (var child in section.GetChildren())
|
||||||
|
{
|
||||||
|
if (string.IsNullOrWhiteSpace(child.Value))
|
||||||
|
continue;
|
||||||
|
|
||||||
|
if (MatchesIpAddress(child.Value, remoteIpAddress))
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
context.Result = new ForbidResult();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static bool MatchesIpAddress(string configIpAddress, IPAddress remoteIpAddress)
|
||||||
|
{
|
||||||
|
if (configIpAddress.Contains('/'))
|
||||||
|
{
|
||||||
|
string[] ipNetworkParts = configIpAddress.Split('/');
|
||||||
|
var ip = IPAddress.Parse(ipNetworkParts.First());
|
||||||
|
int prefix = int.Parse(ipNetworkParts.Last());
|
||||||
|
|
||||||
|
var net = new IPNetwork(ip, prefix);
|
||||||
|
return net.Contains(remoteIpAddress);
|
||||||
|
}
|
||||||
|
|
||||||
|
return IPAddress.Parse(configIpAddress).Equals(remoteIpAddress);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -12,7 +12,7 @@ namespace Microsoft.AspNetCore.Http
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Retrieves the antiforgery token.
|
/// Retrieves the antiforgery token.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="httpContext">The web context.</param>
|
/// <param name="httpContext">The current <see cref="HttpContext"/>.</param>
|
||||||
/// <returns>Name and value of the token.</returns>
|
/// <returns>Name and value of the token.</returns>
|
||||||
public static (string Name, string Value) GetAntiforgeryToken(this HttpContext httpContext)
|
public static (string Name, string Value) GetAntiforgeryToken(this HttpContext httpContext)
|
||||||
{
|
{
|
||||||
@@ -25,7 +25,7 @@ namespace Microsoft.AspNetCore.Http
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Returns the remote ip address.
|
/// Returns the remote ip address.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="httpContext">The web context.</param>
|
/// <param name="httpContext">The current <see cref="HttpContext"/>.</param>
|
||||||
/// <param name="headerName">The name of the header to resolve the <see cref="IPAddress"/> when behind a proxy (Default: X-Forwarded-For).</param>
|
/// <param name="headerName">The name of the header to resolve the <see cref="IPAddress"/> when behind a proxy (Default: X-Forwarded-For).</param>
|
||||||
/// <returns>The ip address of the client.</returns>
|
/// <returns>The ip address of the client.</returns>
|
||||||
public static IPAddress GetRemoteIpAddress(this HttpContext httpContext, string headerName = "X-Forwarded-For")
|
public static IPAddress GetRemoteIpAddress(this HttpContext httpContext, string headerName = "X-Forwarded-For")
|
||||||
@@ -39,10 +39,22 @@ namespace Microsoft.AspNetCore.Http
|
|||||||
return remote;
|
return remote;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Returns whether the request was made locally.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="httpContext">The current <see cref="HttpContext"/>.</param>
|
||||||
|
/// <param name="headerName">The name of the header to resolve the <see cref="IPAddress"/> when behind a proxy (Default: X-Forwarded-For).</param>
|
||||||
|
/// <returns></returns>
|
||||||
|
public static bool IsLocalRequest(this HttpContext httpContext, string headerName = "X-Forwarded-For")
|
||||||
|
{
|
||||||
|
var remoteIpAddress = httpContext.GetRemoteIpAddress(headerName);
|
||||||
|
return httpContext.Connection.LocalIpAddress.Equals(remoteIpAddress);
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Tries to retrieve the return url.
|
/// Tries to retrieve the return url.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="httpContext"></param>
|
/// <param name="httpContext">The current <see cref="HttpContext"/>.</param>
|
||||||
/// <returns></returns>
|
/// <returns></returns>
|
||||||
public static string GetReturnUrl(this HttpContext httpContext)
|
public static string GetReturnUrl(this HttpContext httpContext)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -56,7 +56,7 @@
|
|||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="Unclassified.NetRevisionTask" Version="0.4.2">
|
<PackageReference Include="Unclassified.NetRevisionTask" Version="0.4.3">
|
||||||
<PrivateAssets>all</PrivateAssets>
|
<PrivateAssets>all</PrivateAssets>
|
||||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||||
</PackageReference>
|
</PackageReference>
|
||||||
|
|||||||
@@ -0,0 +1,28 @@
|
|||||||
|
#if NET6_0_OR_GREATER
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
|
||||||
|
|
||||||
|
namespace AMWD.Common.EntityFrameworkCore.Converters
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Defines the conversion from a <see cref="DateOnly"/> object to a <see cref="DateTime"/> which can be handled by the database engine.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// As of 2022-06-04 only required for Microsoft SQL server on .NET 6.0.
|
||||||
|
/// </remarks>
|
||||||
|
public class DateOnlyConverter : ValueConverter<DateOnly, DateTime>
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Initializes a new instance of the <see cref="DateOnlyConverter"/> class.
|
||||||
|
/// </summary>
|
||||||
|
public DateOnlyConverter()
|
||||||
|
: base(
|
||||||
|
d => d.ToDateTime(TimeOnly.MinValue),
|
||||||
|
d => DateOnly.FromDateTime(d)
|
||||||
|
)
|
||||||
|
{ }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
||||||
@@ -0,0 +1,28 @@
|
|||||||
|
#if NET6_0_OR_GREATER
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
|
||||||
|
|
||||||
|
namespace AMWD.Common.EntityFrameworkCore.Converters
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Defines the conversion from a nullable <see cref="DateOnly"/> object to a nullable <see cref="DateTime"/> which can be handled by the database engine.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// As of 2022-06-04 only required for Microsoft SQL server on .NET 6.0.
|
||||||
|
/// </remarks>
|
||||||
|
public class NullableDateOnlyConverter : ValueConverter<DateOnly?, DateTime?>
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Initializes a new instance of the <see cref="NullableDateOnlyConverter"/> class.
|
||||||
|
/// </summary>
|
||||||
|
public NullableDateOnlyConverter()
|
||||||
|
: base(
|
||||||
|
d => d == null ? null : new DateTime?(d.Value.ToDateTime(TimeOnly.MinValue)),
|
||||||
|
d => d == null ? null : new DateOnly?(DateOnly.FromDateTime(d.Value))
|
||||||
|
)
|
||||||
|
{ }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
||||||
@@ -0,0 +1,28 @@
|
|||||||
|
#if NET6_0_OR_GREATER
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
|
||||||
|
|
||||||
|
namespace AMWD.Common.EntityFrameworkCore.Converters
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Defines the conversion from a nullable <see cref="TimeOnly"/> object to a nullable <see cref="TimeSpan"/> which can be handled by the database engine.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// As of 2022-06-04 only required for Microsoft SQL server on .NET 6.0.
|
||||||
|
/// </remarks>
|
||||||
|
public class NullableTimeOnlyConverter : ValueConverter<TimeOnly?, TimeSpan?>
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Initializes a new instance of the <see cref="NullableTimeOnlyConverter"/> class.
|
||||||
|
/// </summary>
|
||||||
|
public NullableTimeOnlyConverter()
|
||||||
|
: base(
|
||||||
|
d => d == null ? null : new TimeSpan?(d.Value.ToTimeSpan()),
|
||||||
|
d => d == null ? null : new TimeOnly?(TimeOnly.FromTimeSpan(d.Value))
|
||||||
|
)
|
||||||
|
{ }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
||||||
@@ -0,0 +1,28 @@
|
|||||||
|
#if NET6_0_OR_GREATER
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
|
||||||
|
|
||||||
|
namespace AMWD.Common.EntityFrameworkCore.Converters
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Defines the conversion from a <see cref="TimeOnly"/> object to a <see cref="TimeSpan"/> which can be handled by the database engine.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// As of 2022-06-04 only required for Microsoft SQL server on .NET 6.0.
|
||||||
|
/// </remarks>
|
||||||
|
public class TimeOnlyConverter : ValueConverter<TimeOnly, TimeSpan>
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Initializes a new instance of the <see cref="TimeOnlyConverter"/> class.
|
||||||
|
/// </summary>
|
||||||
|
public TimeOnlyConverter()
|
||||||
|
: base(
|
||||||
|
t => t.ToTimeSpan(),
|
||||||
|
t => TimeOnly.FromTimeSpan(t)
|
||||||
|
)
|
||||||
|
{ }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
||||||
@@ -0,0 +1,43 @@
|
|||||||
|
#if NET6_0_OR_GREATER
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using AMWD.Common.EntityFrameworkCore.Converters;
|
||||||
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
|
||||||
|
namespace AMWD.Common.EntityFrameworkCore.Extensions
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Extensions for the <see cref="ModelConfigurationBuilder"/> of entity framework core.
|
||||||
|
/// </summary>
|
||||||
|
public static class ModelConfigurationBuilderExtensions
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Adds converters for the <see cref="DateOnly"/> and <see cref="TimeOnly"/> datatypes introduced with .NET 6.0.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// As of 2022-06-04 only required for Microsoft SQL server on .NET 6.0.
|
||||||
|
/// </remarks>
|
||||||
|
/// <param name="builder">The <see cref="ModelConfigurationBuilder"/> instance.</param>
|
||||||
|
/// <returns>The <see cref="ModelConfigurationBuilder"/> instance after applying the converters.</returns>
|
||||||
|
public static ModelConfigurationBuilder AddDateOnlyTimeOnlyConverters(this ModelConfigurationBuilder builder)
|
||||||
|
{
|
||||||
|
builder.Properties<DateOnly>()
|
||||||
|
.HaveConversion<DateOnlyConverter>()
|
||||||
|
.HaveColumnType("date");
|
||||||
|
builder.Properties<DateOnly?>()
|
||||||
|
.HaveConversion<NullableDateOnlyConverter>()
|
||||||
|
.HaveColumnType("date");
|
||||||
|
|
||||||
|
builder.Properties<TimeOnly>()
|
||||||
|
.HaveConversion<TimeOnlyConverter>()
|
||||||
|
.HaveColumnType("time");
|
||||||
|
builder.Properties<TimeOnly?>()
|
||||||
|
.HaveConversion<NullableTimeOnlyConverter>()
|
||||||
|
.HaveColumnType("time");
|
||||||
|
|
||||||
|
return builder;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
||||||
@@ -36,8 +36,8 @@
|
|||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="Moq" Version="4.16.1" />
|
<PackageReference Include="Moq" Version="4.15.1" />
|
||||||
<PackageReference Include="Unclassified.NetRevisionTask" Version="0.4.2">
|
<PackageReference Include="Unclassified.NetRevisionTask" Version="0.4.3">
|
||||||
<PrivateAssets>all</PrivateAssets>
|
<PrivateAssets>all</PrivateAssets>
|
||||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||||
</PackageReference>
|
</PackageReference>
|
||||||
|
|||||||
175
AMWD.Common.Moq/TcpClientMoq.cs
Normal file
175
AMWD.Common.Moq/TcpClientMoq.cs
Normal file
@@ -0,0 +1,175 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Net.Sockets;
|
||||||
|
using System.Threading;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Moq;
|
||||||
|
|
||||||
|
namespace AMWD.Common.Moq
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Wrapps the <see cref="Mock{TcpClient}"/> including the setup.
|
||||||
|
/// </summary>
|
||||||
|
public class TcpClientMoq
|
||||||
|
{
|
||||||
|
private readonly Mock<NetworkStream> streamMock;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Initializes a new instance of the <see cref="TcpClientMoq"/> class.
|
||||||
|
/// </summary>
|
||||||
|
public TcpClientMoq()
|
||||||
|
{
|
||||||
|
Callbacks = new();
|
||||||
|
Response = new byte[0];
|
||||||
|
|
||||||
|
streamMock = new();
|
||||||
|
streamMock
|
||||||
|
.Setup(s => s.WriteAsync(It.IsAny<byte[]>(), It.IsAny<int>(), It.IsAny<int>(), It.IsAny<CancellationToken>()))
|
||||||
|
.Callback<byte[], int, int, CancellationToken>((buffer, offset, count, _) =>
|
||||||
|
{
|
||||||
|
var callback = new TcpClientCallback
|
||||||
|
{
|
||||||
|
Buffer = new byte[count],
|
||||||
|
Offset = offset,
|
||||||
|
Count = count,
|
||||||
|
Type = TcpClientCallback.WriteType.Asynchronous
|
||||||
|
};
|
||||||
|
Array.Copy(buffer, offset, callback.Buffer, 0, count);
|
||||||
|
|
||||||
|
Callbacks.Add(callback);
|
||||||
|
})
|
||||||
|
.Returns(Task.CompletedTask);
|
||||||
|
streamMock
|
||||||
|
.Setup(s => s.Write(It.IsAny<byte[]>(), It.IsAny<int>(), It.IsAny<int>()))
|
||||||
|
.Callback<byte[], int, int>((buffer, offset, count) =>
|
||||||
|
{
|
||||||
|
var callback = new TcpClientCallback
|
||||||
|
{
|
||||||
|
Buffer = new byte[count],
|
||||||
|
Offset = offset,
|
||||||
|
Count = count,
|
||||||
|
Type = TcpClientCallback.WriteType.Synchronous
|
||||||
|
};
|
||||||
|
Array.Copy(buffer, offset, callback.Buffer, 0, count);
|
||||||
|
|
||||||
|
Callbacks.Add(callback);
|
||||||
|
});
|
||||||
|
|
||||||
|
streamMock
|
||||||
|
.Setup(s => s.ReadAsync(It.IsAny<byte[]>(), It.IsAny<int>(), It.IsAny<int>(), It.IsAny<CancellationToken>()))
|
||||||
|
.Callback<byte[], int, int, CancellationToken>((buffer, offset, count, _) =>
|
||||||
|
{
|
||||||
|
byte[] bytes = Response ?? new byte[0];
|
||||||
|
Array.Copy(bytes, 0, buffer, offset, Math.Min(bytes.Length, count));
|
||||||
|
})
|
||||||
|
.ReturnsAsync(Response?.Length ?? 0);
|
||||||
|
streamMock
|
||||||
|
.Setup(s => s.Read(It.IsAny<byte[]>(), It.IsAny<int>(), It.IsAny<int>()))
|
||||||
|
.Callback<byte[], int, int>((buffer, offset, count) =>
|
||||||
|
{
|
||||||
|
byte[] bytes = Response ?? new byte[0];
|
||||||
|
Array.Copy(bytes, 0, buffer, offset, Math.Min(bytes.Length, count));
|
||||||
|
})
|
||||||
|
.Returns(Response?.Length ?? 0);
|
||||||
|
|
||||||
|
Mock = new();
|
||||||
|
Mock
|
||||||
|
.Setup(c => c.GetStream())
|
||||||
|
.Returns(streamMock.Object);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the mocked <see cref="TcpClient"/>.
|
||||||
|
/// </summary>
|
||||||
|
public Mock<TcpClient> Mock { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the placed request.
|
||||||
|
/// </summary>
|
||||||
|
public List<TcpClientCallback> Callbacks { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the byte response, that should be "sent".
|
||||||
|
/// </summary>
|
||||||
|
public byte[] Response { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Resets the <see cref="Response"/> and <see cref="Callbacks"/>.
|
||||||
|
/// </summary>
|
||||||
|
public void Reset()
|
||||||
|
{
|
||||||
|
Response = new byte[0];
|
||||||
|
Callbacks.Clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Verifies the number of calls writing asynchronous to the stream.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="times">Number of calls.</param>
|
||||||
|
public void VerifyWriteAsync(Times times)
|
||||||
|
=> streamMock.Verify(s => s.WriteAsync(It.IsAny<byte[]>(), It.IsAny<int>(), It.IsAny<int>(), It.IsAny<CancellationToken>()), times);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Verifies the number of calls writing synchronous to the stream.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="times">Number of calls.</param>
|
||||||
|
public void VerifyWriteSync(Times times)
|
||||||
|
=> streamMock.Verify(s => s.Write(It.IsAny<byte[]>(), It.IsAny<int>(), It.IsAny<int>()), times);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Verifies the number of calls reading asynchronous from the stream.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="times">Number of calls.</param>
|
||||||
|
public void VerifyReadAsync(Times times)
|
||||||
|
=> streamMock.Verify(s => s.ReadAsync(It.IsAny<byte[]>(), It.IsAny<int>(), It.IsAny<int>(), It.IsAny<CancellationToken>()), times);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Verifies the number of calls reading synchronous from the stream.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="times">Number of calls.</param>
|
||||||
|
public void VerifyReadSync(Times times)
|
||||||
|
=> streamMock.Verify(s => s.Read(It.IsAny<byte[]>(), It.IsAny<int>(), It.IsAny<int>()), times);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Represents the placed TCP request.
|
||||||
|
/// </summary>
|
||||||
|
public class TcpClientCallback
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the type (a/synchronous call).
|
||||||
|
/// </summary>
|
||||||
|
public WriteType Type { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the buffer content.
|
||||||
|
/// </summary>
|
||||||
|
public byte[] Buffer { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the offset.
|
||||||
|
/// </summary>
|
||||||
|
public int Offset { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the byte count.
|
||||||
|
/// </summary>
|
||||||
|
public int Count { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Lists the possible request types.
|
||||||
|
/// </summary>
|
||||||
|
public enum WriteType
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// The request was synchronous.
|
||||||
|
/// </summary>
|
||||||
|
Synchronous = 1,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The request was asynchronous.
|
||||||
|
/// </summary>
|
||||||
|
Asynchronous = 2
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -7,13 +7,13 @@
|
|||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="coverlet.msbuild" Version="3.1.0">
|
<PackageReference Include="coverlet.msbuild" 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>
|
||||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.0.0" />
|
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.2.0" />
|
||||||
<PackageReference Include="MSTest.TestAdapter" Version="2.2.7" />
|
<PackageReference Include="MSTest.TestAdapter" Version="2.2.10" />
|
||||||
<PackageReference Include="MSTest.TestFramework" Version="2.2.7" />
|
<PackageReference Include="MSTest.TestFramework" Version="2.2.10" />
|
||||||
<PackageReference Include="ReflectionMagic" Version="4.1.0" />
|
<PackageReference Include="ReflectionMagic" Version="4.1.0" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
|
|||||||
@@ -38,8 +38,8 @@
|
|||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="Dns" Version="7.0.0" />
|
<PackageReference Include="Dns" Version="7.0.0" />
|
||||||
<PackageReference Include="Newtonsoft.Json" Version="10.0.1" />
|
<PackageReference Include="Newtonsoft.Json" Version="10.0.1" />
|
||||||
<PackageReference Include="Unclassified.DeepConvert" Version="1.3.0" />
|
<PackageReference Include="Unclassified.DeepConvert" Version="1.4.0" />
|
||||||
<PackageReference Include="Unclassified.NetRevisionTask" Version="0.4.2">
|
<PackageReference Include="Unclassified.NetRevisionTask" Version="0.4.3">
|
||||||
<PrivateAssets>all</PrivateAssets>
|
<PrivateAssets>all</PrivateAssets>
|
||||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||||
</PackageReference>
|
</PackageReference>
|
||||||
|
|||||||
@@ -4,9 +4,16 @@ All notable changes to this project will be documented in this file.
|
|||||||
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
||||||
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
||||||
|
|
||||||
## [Unreleased](https://git.am-wd.de/AM.WD/common/compare/v1.4.3...master) - 0000-00-00
|
## [Unreleased](https://git.am-wd.de/AM.WD/common/compare/v1.5.0...master) - 0000-00-00
|
||||||
_nothing changed yet_
|
_nothing changed yet_
|
||||||
|
|
||||||
|
## [v1.5.0](https://git.am-wd.de/AM.WD/common/compare/v1.4.3...v1.5.0) - 2022-06-15
|
||||||
|
### Added
|
||||||
|
- `TcpClientMoq` to test communication via a `TcpClient`
|
||||||
|
- EntityFramework Core Converters for new `DateOnly` and `TimeOnly` datatypes when using SQL Server on .NET 6.0 (Bug on Microsoft's EntityFramework)
|
||||||
|
- `HttpContext.IsLocalRequest()` to determine whether the request was from local or remote.
|
||||||
|
- `IPWhitelistAttribute` and `IPBlacklistAttribute` to allow/restrict access on specific controllers/actions.
|
||||||
|
|
||||||
|
|
||||||
## [v1.4.3](https://git.am-wd.de/AM.WD/common/compare/v1.4.2...v1.4.3) - 2022-05-12
|
## [v1.4.3](https://git.am-wd.de/AM.WD/common/compare/v1.4.2...v1.4.3) - 2022-05-12
|
||||||
### Fixed
|
### Fixed
|
||||||
|
|||||||
Reference in New Issue
Block a user