Kleinere Änderungen, Dokumentation und Git-Dateien
This commit is contained in:
@@ -19,7 +19,10 @@
|
|||||||
|
|
||||||
<PackageId>AMWD.Common.AspNetCore</PackageId>
|
<PackageId>AMWD.Common.AspNetCore</PackageId>
|
||||||
<PackageIcon>icon.png</PackageIcon>
|
<PackageIcon>icon.png</PackageIcon>
|
||||||
<PackageProjectUrl>https://git.am-wd.de/AM.WD/common</PackageProjectUrl>
|
<PackageProjectUrl>https://wiki.am-wd.de/libs/common</PackageProjectUrl>
|
||||||
|
|
||||||
|
<RepositoryType>git</RepositoryType>
|
||||||
|
<RepositoryUrl>https://git.am-wd.de/AM.WD/common.git</RepositoryUrl>
|
||||||
|
|
||||||
<Product>AM.WD Common Library for ASP.NET Core</Product>
|
<Product>AM.WD Common Library for ASP.NET Core</Product>
|
||||||
<Description>Library with classes and extensions used frequently on AM.WD projects.</Description>
|
<Description>Library with classes and extensions used frequently on AM.WD projects.</Description>
|
||||||
|
|||||||
@@ -14,9 +14,8 @@ namespace Microsoft.AspNetCore.Builder
|
|||||||
/// Adds settings to run behind a reverse proxy (e.g. NginX).
|
/// Adds settings to run behind a reverse proxy (e.g. NginX).
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <remarks>
|
/// <remarks>
|
||||||
/// A base path (e.g. running in a sub-directory /app) for the application is defined via<br/>
|
/// A base path (e.g. running in a sub-directory /app) for the application is defined via <c>ASPNETCORE_APPL_PATH</c> environment variable.
|
||||||
/// - <c>ASPNETCORE_APPL_PATH</c> environment variable (preferred)<br/>
|
/// <br/>
|
||||||
/// - <c>AspNetCore_Appl_Path</c> in the settings file<br/>
|
|
||||||
/// <br/>
|
/// <br/>
|
||||||
/// Additionally you can specify the proxy server by using <paramref name="address"/> or a <paramref name="network"/> when there are multiple proxy servers.
|
/// Additionally you can specify the proxy server by using <paramref name="address"/> or a <paramref name="network"/> when there are multiple proxy servers.
|
||||||
/// <br/>
|
/// <br/>
|
||||||
|
|||||||
@@ -19,9 +19,7 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding
|
|||||||
/// <param name="keyExpression">The <see cref="MemberExpression"/> that specifies the property.</param>
|
/// <param name="keyExpression">The <see cref="MemberExpression"/> that specifies the property.</param>
|
||||||
/// <param name="errorMessage">The error message to add.</param>
|
/// <param name="errorMessage">The error message to add.</param>
|
||||||
/// <exception cref="InvalidOperationException">No member expression provided.</exception>
|
/// <exception cref="InvalidOperationException">No member expression provided.</exception>
|
||||||
#pragma warning disable IDE0060 // remove unused parameters
|
|
||||||
public static void AddModelError<TModel, TProperty>(this ModelStateDictionary modelState, TModel model, Expression<Func<TModel, TProperty>> keyExpression, string errorMessage)
|
public static void AddModelError<TModel, TProperty>(this ModelStateDictionary modelState, TModel model, Expression<Func<TModel, TProperty>> keyExpression, string errorMessage)
|
||||||
#pragma warning restore IDE0060 // remove unused parameters
|
|
||||||
{
|
{
|
||||||
if (modelState is null)
|
if (modelState is null)
|
||||||
throw new ArgumentNullException(nameof(modelState));
|
throw new ArgumentNullException(nameof(modelState));
|
||||||
|
|||||||
@@ -9,24 +9,24 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Custom floating point ModelBinder as the team of Microsoft is not capable of fixing their <see href="https://github.com/dotnet/aspnetcore/issues/6566">issue</see> with other cultures than en-US.
|
/// Custom floating point ModelBinder as the team of Microsoft is not capable of fixing their <see href="https://github.com/dotnet/aspnetcore/issues/6566">issue</see> with other cultures than en-US.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class CustomFloatingPointModelBinder : IModelBinder
|
public class InvariantFloatingPointModelBinder : IModelBinder
|
||||||
{
|
{
|
||||||
private readonly NumberStyles supportedNumberStyles;
|
private readonly NumberStyles supportedNumberStyles;
|
||||||
private readonly ILogger logger;
|
private readonly ILogger logger;
|
||||||
private readonly CultureInfo cultureInfo;
|
private readonly CultureInfo cultureInfo;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Initializes a new instance of <see cref="CustomFloatingPointModelBinder"/>.
|
/// Initializes a new instance of <see cref="InvariantFloatingPointModelBinder"/>.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="supportedStyles">The <see cref="NumberStyles"/>.</param>
|
/// <param name="supportedStyles">The <see cref="NumberStyles"/>.</param>
|
||||||
/// <param name="cultureInfo">The <see cref="CultureInfo"/>.</param>
|
/// <param name="cultureInfo">The <see cref="CultureInfo"/>.</param>
|
||||||
/// <param name="loggerFactory">The <see cref="ILoggerFactory"/>.</param>
|
/// <param name="loggerFactory">The <see cref="ILoggerFactory"/>.</param>
|
||||||
public CustomFloatingPointModelBinder(NumberStyles supportedStyles, CultureInfo cultureInfo, ILoggerFactory loggerFactory)
|
public InvariantFloatingPointModelBinder(NumberStyles supportedStyles, CultureInfo cultureInfo, ILoggerFactory loggerFactory)
|
||||||
{
|
{
|
||||||
this.cultureInfo = cultureInfo ?? throw new ArgumentNullException(nameof(cultureInfo));
|
this.cultureInfo = cultureInfo ?? throw new ArgumentNullException(nameof(cultureInfo));
|
||||||
|
|
||||||
supportedNumberStyles = supportedStyles;
|
supportedNumberStyles = supportedStyles;
|
||||||
logger = loggerFactory?.CreateLogger<CustomFloatingPointModelBinder>();
|
logger = loggerFactory?.CreateLogger<InvariantFloatingPointModelBinder>();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
@@ -20,7 +20,7 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding
|
|||||||
/// options.ModelBinderProviders.Insert(0, new CustomFloatingPointModelBinderProvider());<br/>
|
/// options.ModelBinderProviders.Insert(0, new CustomFloatingPointModelBinderProvider());<br/>
|
||||||
/// });</code>
|
/// });</code>
|
||||||
/// </remarks>
|
/// </remarks>
|
||||||
public class CustomFloatingPointModelBinderProvider : IModelBinderProvider
|
public class InvariantFloatingPointModelBinderProvider : IModelBinderProvider
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets or sets the supported <see cref="NumberStyles"/> globally.
|
/// Gets or sets the supported <see cref="NumberStyles"/> globally.
|
||||||
@@ -49,7 +49,7 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding
|
|||||||
modelType == typeof(double) ||
|
modelType == typeof(double) ||
|
||||||
modelType == typeof(float))
|
modelType == typeof(float))
|
||||||
{
|
{
|
||||||
return new CustomFloatingPointModelBinder(SupportedNumberStyles, CultureInfo, loggerFactory);
|
return new InvariantFloatingPointModelBinder(SupportedNumberStyles, CultureInfo, loggerFactory);
|
||||||
}
|
}
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
@@ -19,7 +19,10 @@
|
|||||||
|
|
||||||
<PackageId>AMWD.Common.EntityFrameworkCore</PackageId>
|
<PackageId>AMWD.Common.EntityFrameworkCore</PackageId>
|
||||||
<PackageIcon>icon.png</PackageIcon>
|
<PackageIcon>icon.png</PackageIcon>
|
||||||
<PackageProjectUrl>https://git.am-wd.de/AM.WD/common</PackageProjectUrl>
|
<PackageProjectUrl>https://wiki.am-wd.de/libs/common</PackageProjectUrl>
|
||||||
|
|
||||||
|
<RepositoryType>git</RepositoryType>
|
||||||
|
<RepositoryUrl>https://git.am-wd.de/AM.WD/common.git</RepositoryUrl>
|
||||||
|
|
||||||
<Product>AM.WD Common Library for EntityFramework Core</Product>
|
<Product>AM.WD Common Library for EntityFramework Core</Product>
|
||||||
<Description>Library with classes and extensions used frequently on AM.WD projects.</Description>
|
<Description>Library with classes and extensions used frequently on AM.WD projects.</Description>
|
||||||
|
|||||||
@@ -19,7 +19,10 @@
|
|||||||
|
|
||||||
<PackageId>AMWD.Common</PackageId>
|
<PackageId>AMWD.Common</PackageId>
|
||||||
<PackageIcon>icon.png</PackageIcon>
|
<PackageIcon>icon.png</PackageIcon>
|
||||||
<PackageProjectUrl>https://git.am-wd.de/AM.WD/common</PackageProjectUrl>
|
<PackageProjectUrl>https://wiki.am-wd.de/libs/common</PackageProjectUrl>
|
||||||
|
|
||||||
|
<RepositoryType>git</RepositoryType>
|
||||||
|
<RepositoryUrl>https://git.am-wd.de/AM.WD/common.git</RepositoryUrl>
|
||||||
|
|
||||||
<Product>AM.WD Common Library</Product>
|
<Product>AM.WD Common Library</Product>
|
||||||
<Description>Library with classes and extensions used frequently on AM.WD projects.</Description>
|
<Description>Library with classes and extensions used frequently on AM.WD projects.</Description>
|
||||||
|
|||||||
@@ -5,16 +5,20 @@ using System.Threading.Tasks;
|
|||||||
|
|
||||||
namespace AMWD.Common.Utilities
|
namespace AMWD.Common.Utilities
|
||||||
{
|
{
|
||||||
|
// originally inspired by a code from Yves Goergen (unclassified.software).
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Implements an awaitable task that runs after a specified delay. The delay can be reset
|
/// Implements an awaitable task that runs after a specified delay. The delay can be reset.
|
||||||
/// before and after the task has run. By resetting the delay, the task can be executed multiple
|
/// By resetting the delay, the task can be executed multiple times.
|
||||||
/// times. The scheduled or executing or last executed task can be awaited, until the delay is
|
|
||||||
/// reset. After that, the next execution can be awaited.
|
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class DelayedTask
|
public class DelayedTask
|
||||||
{
|
{
|
||||||
#region Data
|
#region Data
|
||||||
|
|
||||||
|
private Timer timer;
|
||||||
|
|
||||||
|
private bool nextRunPending;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The synchronisation object.
|
/// The synchronisation object.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -25,7 +29,15 @@ namespace AMWD.Common.Utilities
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
protected Action<Exception> exceptionHandler;
|
protected Action<Exception> exceptionHandler;
|
||||||
|
|
||||||
private Timer timer;
|
/// <summary>
|
||||||
|
/// Provides the <see cref="Task"/> for the <see cref="GetAwaiter"/> method.
|
||||||
|
/// </summary>
|
||||||
|
protected TaskCompletionSourceWrapper tcs;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the action to execute.
|
||||||
|
/// </summary>
|
||||||
|
protected Action Action { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets a value indicating whether the timer is running and an execution is scheduled. This
|
/// Gets a value indicating whether the timer is running and an execution is scheduled. This
|
||||||
@@ -39,22 +51,6 @@ namespace AMWD.Common.Utilities
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public bool IsRunning { get; private set; }
|
public bool IsRunning { get; private set; }
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Indicates whether the action shall be executed again after the currently ongoing
|
|
||||||
/// execution has completed.
|
|
||||||
/// </summary>
|
|
||||||
private bool nextRunPending;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Provides the <see cref="Task"/> for the <see cref="GetAwaiter"/> method.
|
|
||||||
/// </summary>
|
|
||||||
protected TaskCompletionSourceWrapper tcs;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets or sets the action to execute.
|
|
||||||
/// </summary>
|
|
||||||
protected Action Action { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets or sets the delay to wait before executing the action.
|
/// Gets or sets the delay to wait before executing the action.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -66,53 +62,41 @@ namespace AMWD.Common.Utilities
|
|||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Creates a new task instance that executes the specified action after the delay, but does
|
/// Creates a new task instance that executes the specified action after the delay, but does
|
||||||
/// not start it yet. Multiple executions are allowed when calling <see cref="Reset"/> after
|
/// not start it yet.
|
||||||
/// the executed was started.
|
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="action">The action to execute.</param>
|
/// <param name="action">The action to execute.</param>
|
||||||
/// <param name="delay">The delay.</param>
|
/// <param name="delay">The delay.</param>
|
||||||
/// <returns></returns>
|
/// <returns></returns>
|
||||||
public static DelayedTask Create(Action action, TimeSpan delay)
|
public static DelayedTask Create(Action action, TimeSpan delay)
|
||||||
{
|
=> new() { Action = action, Delay = delay };
|
||||||
return new DelayedTask { Action = action, Delay = delay };
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Creates a new task instance that executes the specified action after the delay, but does
|
/// Creates a new task instance that executes the specified action after the delay, but does
|
||||||
/// not start it yet. Multiple executions are allowed when calling <see cref="Reset"/> after
|
/// not start it yet.
|
||||||
/// the executed was started.
|
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="action">The action to execute.</param>
|
/// <param name="action">The action to execute.</param>
|
||||||
/// <param name="delay">The delay.</param>
|
/// <param name="delay">The delay.</param>
|
||||||
/// <returns></returns>
|
/// <returns></returns>
|
||||||
public static DelayedTaskWithResult<TResult> Create<TResult>(Func<TResult> action, TimeSpan delay)
|
public static DelayedTaskWithResult<TResult> Create<TResult>(Func<TResult> action, TimeSpan delay)
|
||||||
{
|
=> DelayedTaskWithResult<TResult>.Create(action, delay);
|
||||||
return DelayedTaskWithResult<TResult>.Create(action, delay);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Executes the specified action after the delay. Multiple executions are allowed when
|
/// Executes the specified action after the delay.
|
||||||
/// calling <see cref="Reset"/> after the executed was started.
|
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="action">The action to execute.</param>
|
/// <param name="action">The action to execute.</param>
|
||||||
/// <param name="delay">The delay.</param>
|
/// <param name="delay">The delay.</param>
|
||||||
/// <returns></returns>
|
/// <returns></returns>
|
||||||
public static DelayedTask Run(Action action, TimeSpan delay)
|
public static DelayedTask Run(Action action, TimeSpan delay)
|
||||||
{
|
=> new DelayedTask { Action = action, Delay = delay }.Start();
|
||||||
return new DelayedTask { Action = action, Delay = delay }.Start();
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Executes the specified action after the delay. Multiple executions are allowed when
|
/// Executes the specified action after the delay.
|
||||||
/// calling <see cref="Reset"/> after the executed was started.
|
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="action">The action to execute.</param>
|
/// <param name="action">The action to execute.</param>
|
||||||
/// <param name="delay">The delay.</param>
|
/// <param name="delay">The delay.</param>
|
||||||
/// <returns></returns>
|
/// <returns></returns>
|
||||||
public static DelayedTaskWithResult<TResult> Run<TResult>(Func<TResult> action, TimeSpan delay)
|
public static DelayedTaskWithResult<TResult> Run<TResult>(Func<TResult> action, TimeSpan delay)
|
||||||
{
|
=> DelayedTaskWithResult<TResult>.Run(action, delay);
|
||||||
return DelayedTaskWithResult<TResult>.Run(action, delay);
|
|
||||||
}
|
|
||||||
|
|
||||||
#endregion Static methods
|
#endregion Static methods
|
||||||
|
|
||||||
@@ -158,7 +142,7 @@ namespace AMWD.Common.Utilities
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Cancels the delay. Any pending executions are cleared. If the action was pending but not
|
/// Cancels the delay. Any pending execution is cleared. If the action was pending but not
|
||||||
/// yet executing, this task is cancelled. If the action was not pending or is already
|
/// yet executing, this task is cancelled. If the action was not pending or is already
|
||||||
/// executing, this task will be completed successfully after the action has completed.
|
/// executing, this task will be completed successfully after the action has completed.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -185,13 +169,7 @@ namespace AMWD.Common.Utilities
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Starts a pending execution immediately, not waiting for the timer to elapse.
|
/// Starts a pending execution immediately, not waiting for the timer to elapse.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <returns>true, if an execution was started; otherwise, false.</returns>
|
/// <returns><c>true</c>, if an execution was started; otherwise, <c>false</c>.</returns>
|
||||||
/// <remarks>
|
|
||||||
/// A new execution is only started if one is currently waiting to run, or already running.
|
|
||||||
/// In the former case, the execution is scheduled immediately with the timer; in the latter
|
|
||||||
/// case, it is scheduled for when the currently running execution has completed. If an
|
|
||||||
/// execution has been started (the method returned true), it can be awaited normally.
|
|
||||||
/// </remarks>
|
|
||||||
public bool ExecutePending()
|
public bool ExecutePending()
|
||||||
{
|
{
|
||||||
lock (syncObj)
|
lock (syncObj)
|
||||||
@@ -287,9 +265,7 @@ namespace AMWD.Common.Utilities
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
/// <returns></returns>
|
/// <returns></returns>
|
||||||
protected virtual TaskCompletionSourceWrapper CreateTcs()
|
protected virtual TaskCompletionSourceWrapper CreateTcs()
|
||||||
{
|
=> new TaskCompletionSourceWrapper<object>();
|
||||||
return new TaskCompletionSourceWrapper<object>();
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Called when the timer has elapsed.
|
/// Called when the timer has elapsed.
|
||||||
@@ -366,18 +342,19 @@ namespace AMWD.Common.Utilities
|
|||||||
// Unblock waiters if not already waiting for the next execution.
|
// Unblock waiters if not already waiting for the next execution.
|
||||||
// This task can be awaited again after the Reset method has been called.
|
// This task can be awaited again after the Reset method has been called.
|
||||||
if (exception != null)
|
if (exception != null)
|
||||||
|
{
|
||||||
localTcs?.TrySetException(exception);
|
localTcs?.TrySetException(exception);
|
||||||
|
}
|
||||||
else
|
else
|
||||||
|
{
|
||||||
SetLastResult(localTcs);
|
SetLastResult(localTcs);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Runs the action of the task.
|
/// Runs the action of the task.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
protected virtual void Run()
|
protected virtual void Run() => Action();
|
||||||
{
|
|
||||||
Action();
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Sets the <see cref="TaskCompletionSourceWrapper"/> result from the last action.
|
/// Sets the <see cref="TaskCompletionSourceWrapper"/> result from the last action.
|
||||||
@@ -447,31 +424,13 @@ namespace AMWD.Common.Utilities
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="result">The result value to bind to this <see cref="Task{TResult}"/>.</param>
|
/// <param name="result">The result value to bind to this <see cref="Task{TResult}"/>.</param>
|
||||||
/// <seealso cref="TaskCompletionSource{TResult}.TrySetResult(TResult)"/>
|
/// <seealso cref="TaskCompletionSource{TResult}.TrySetResult(TResult)"/>
|
||||||
public void TrySetResult(TResult result)
|
public void TrySetResult(TResult result) => tcs.TrySetResult(result);
|
||||||
{
|
|
||||||
tcs.TrySetResult(result);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
/// <inheritdoc/>
|
||||||
/// Attempts to transition the underlying <see cref="Task{TResult}"/> into the
|
public override void TrySetException(Exception exception) => tcs.TrySetException(exception);
|
||||||
/// <see cref="TaskStatus.Faulted"/> state and binds it to a specified exception.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="exception">The exception to bind to this <see cref="Task{TResult}"/>.</param>
|
|
||||||
/// <seealso cref="TaskCompletionSource{TResult}.TrySetException(Exception)"/>
|
|
||||||
public override void TrySetException(Exception exception)
|
|
||||||
{
|
|
||||||
tcs.TrySetException(exception);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
/// <inheritdoc/>
|
||||||
/// Attempts to transition the underlying <see cref="Task{TResult}"/> into the
|
public override void TrySetCanceled() => tcs.TrySetCanceled();
|
||||||
/// <see cref="TaskStatus.Canceled"/> state.
|
|
||||||
/// </summary>
|
|
||||||
/// <seealso cref="TaskCompletionSource{TResult}.TrySetCanceled()"/>
|
|
||||||
public override void TrySetCanceled()
|
|
||||||
{
|
|
||||||
tcs.TrySetCanceled();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#endregion Internal TaskCompletionSourceWrapper classes
|
#endregion Internal TaskCompletionSourceWrapper classes
|
||||||
@@ -497,36 +456,22 @@ namespace AMWD.Common.Utilities
|
|||||||
protected new Func<TResult> Action { get; set; }
|
protected new Func<TResult> Action { get; set; }
|
||||||
|
|
||||||
internal static DelayedTaskWithResult<TResult> Create(Func<TResult> action, TimeSpan delay)
|
internal static DelayedTaskWithResult<TResult> Create(Func<TResult> action, TimeSpan delay)
|
||||||
{
|
=> new() { Action = action, Delay = delay };
|
||||||
return new DelayedTaskWithResult<TResult> { Action = action, Delay = delay };
|
|
||||||
}
|
|
||||||
|
|
||||||
internal static DelayedTaskWithResult<TResult> Run(Func<TResult> action, TimeSpan delay)
|
internal static DelayedTaskWithResult<TResult> Run(Func<TResult> action, TimeSpan delay)
|
||||||
{
|
=> (DelayedTaskWithResult<TResult>)new DelayedTaskWithResult<TResult> { Action = action, Delay = delay }.Start();
|
||||||
return (DelayedTaskWithResult<TResult>)new DelayedTaskWithResult<TResult> { Action = action, Delay = delay }.Start();
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
/// <inheritdoc/>
|
||||||
/// Creates a <see cref="DelayedTask.TaskCompletionSourceWrapper"/> instance.
|
|
||||||
/// </summary>
|
|
||||||
/// <returns></returns>
|
|
||||||
protected override TaskCompletionSourceWrapper CreateTcs()
|
protected override TaskCompletionSourceWrapper CreateTcs()
|
||||||
{
|
=> new TaskCompletionSourceWrapper<TResult>();
|
||||||
return new TaskCompletionSourceWrapper<TResult>();
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
/// <inheritdoc/>
|
||||||
/// Runs the action of the task.
|
|
||||||
/// </summary>
|
|
||||||
protected override void Run()
|
protected override void Run()
|
||||||
{
|
{
|
||||||
lastResult = Action();
|
lastResult = Action();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <inheritdoc/>
|
||||||
/// Sets the <see cref="DelayedTask.TaskCompletionSourceWrapper"/> result from the last action.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="tcs">The <see cref="DelayedTask.TaskCompletionSourceWrapper"/> to set the result of.</param>
|
|
||||||
protected override void SetLastResult(TaskCompletionSourceWrapper tcs)
|
protected override void SetLastResult(TaskCompletionSourceWrapper tcs)
|
||||||
{
|
{
|
||||||
var myTcs = (TaskCompletionSourceWrapper<TResult>)tcs;
|
var myTcs = (TaskCompletionSourceWrapper<TResult>)tcs;
|
||||||
@@ -570,9 +515,7 @@ namespace AMWD.Common.Utilities
|
|||||||
/// <param name="delayedTask">The <see cref="DelayedTaskWithResult{TResult}"/> instance to cast.</param>
|
/// <param name="delayedTask">The <see cref="DelayedTaskWithResult{TResult}"/> instance to cast.</param>
|
||||||
/// <returns>A <see cref="Task{TResult}"/> that represents the current awaitable operation.</returns>
|
/// <returns>A <see cref="Task{TResult}"/> that represents the current awaitable operation.</returns>
|
||||||
public static implicit operator Task<TResult>(DelayedTaskWithResult<TResult> delayedTask)
|
public static implicit operator Task<TResult>(DelayedTaskWithResult<TResult> delayedTask)
|
||||||
{
|
=> delayedTask.Task;
|
||||||
return delayedTask.Task;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Adds an unhandled exception handler to this <see cref="DelayedTask"/> instance.
|
/// Adds an unhandled exception handler to this <see cref="DelayedTask"/> instance.
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
using System;
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Net;
|
using System.Net;
|
||||||
using System.Net.NetworkInformation;
|
using System.Net.NetworkInformation;
|
||||||
@@ -12,70 +13,93 @@ namespace AMWD.Common.Utilities
|
|||||||
public static class NetworkHelper
|
public static class NetworkHelper
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Tries to resolve a <paramref name="hostname"/> into an <see cref="IPAddress"/> to connect to.
|
/// Tries to resolve a <paramref name="hostname"/> to its <see cref="IPAddress"/>es.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="hostname">The hostname to resolve.</param>
|
/// <param name="hostname">The hostname to resolve.</param>
|
||||||
/// <param name="addressFamily">An address family to use (available: <see cref="AddressFamily.InterNetwork"/> and <see cref="AddressFamily.InterNetworkV6"/>).</param>
|
/// <param name="addressFamily">An address family to use (available: <see cref="AddressFamily.InterNetwork"/> and <see cref="AddressFamily.InterNetworkV6"/>).</param>
|
||||||
/// <param name="fallback">The fallback ip address when resolving failed.</param>
|
/// <returns>The resolved <see cref="IPAddress"/>es or an empty list.</returns>
|
||||||
/// <returns>The resolved <see cref="IPAddress"/> to connect to or <paramref name="fallback"/> value.</returns>
|
public static List<IPAddress> ResolveHost(string hostname, AddressFamily addressFamily = default)
|
||||||
public static IPAddress ResolveHost(string hostname, AddressFamily addressFamily = AddressFamily.Unspecified, IPAddress fallback = null)
|
|
||||||
{
|
{
|
||||||
if (IPAddress.TryParse(hostname, out var ipAddress))
|
if (string.IsNullOrWhiteSpace(hostname))
|
||||||
{
|
return new();
|
||||||
if (ipAddress.AddressFamily != AddressFamily.InterNetwork && ipAddress.AddressFamily != AddressFamily.InterNetworkV6)
|
|
||||||
return fallback;
|
|
||||||
|
|
||||||
if (addressFamily != AddressFamily.Unspecified && ipAddress.AddressFamily != addressFamily)
|
if (addressFamily != AddressFamily.InterNetwork && addressFamily != AddressFamily.InterNetworkV6)
|
||||||
return fallback;
|
addressFamily = AddressFamily.Unspecified;
|
||||||
|
|
||||||
return ipAddress;
|
var ipAddress = ResolveIpAddress(hostname, addressFamily);
|
||||||
}
|
// the name was an ip address, should not happen but experience tells other stories
|
||||||
|
if (ipAddress != null)
|
||||||
return Dns.GetHostAddresses(hostname)
|
return new() { ipAddress };
|
||||||
.Where(a => a.AddressFamily == AddressFamily.InterNetwork || a.AddressFamily == AddressFamily.InterNetworkV6)
|
|
||||||
.Where(a => addressFamily == AddressFamily.Unspecified || a.AddressFamily == addressFamily)
|
|
||||||
.OrderBy(a => a.AddressFamily)
|
|
||||||
.FirstOrDefault() ?? fallback;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Tries to resolve a <paramref name="iface"/> into an <see cref="IPAddress"/> to bind (listen) on.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="iface">The interface name to resolve.</param>
|
|
||||||
/// <param name="addressFamily">An address family to use (available: <see cref="AddressFamily.InterNetwork"/> and <see cref="AddressFamily.InterNetworkV6"/>).</param>
|
|
||||||
/// <param name="fallback">The fallback ip address when resolving failed.</param>
|
|
||||||
/// <returns>The resolved <see cref="IPAddress"/> to bind on or <paramref name="fallback"/> value.</returns>
|
|
||||||
public static IPAddress ResolveInterface(string iface, AddressFamily addressFamily = AddressFamily.Unspecified, IPAddress fallback = null)
|
|
||||||
{
|
|
||||||
if (IPAddress.TryParse(iface, out var ipAddress))
|
|
||||||
{
|
|
||||||
if (ipAddress.AddressFamily != AddressFamily.InterNetwork && ipAddress.AddressFamily != AddressFamily.InterNetworkV6)
|
|
||||||
return fallback;
|
|
||||||
|
|
||||||
if (addressFamily != AddressFamily.Unspecified && ipAddress.AddressFamily != addressFamily)
|
|
||||||
return fallback;
|
|
||||||
|
|
||||||
return ipAddress;
|
|
||||||
}
|
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
return Dns.GetHostAddresses(iface)
|
return Dns.GetHostAddresses(hostname)
|
||||||
.Where(a => a.AddressFamily == AddressFamily.InterNetwork || a.AddressFamily == AddressFamily.InterNetworkV6)
|
.FilterAddressFamily(addressFamily)
|
||||||
.Where(a => addressFamily == AddressFamily.Unspecified || a.AddressFamily == addressFamily)
|
.ToList();
|
||||||
.OrderBy(a => a.AddressFamily)
|
|
||||||
.FirstOrDefault() ?? fallback;
|
|
||||||
}
|
}
|
||||||
catch (SocketException)
|
catch
|
||||||
|
{
|
||||||
|
return new();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Tries to resolve an interface name to its v4 and v6 <see cref="IPAddress"/>es.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="interfaceName">The interface name to resolve.</param>
|
||||||
|
/// <param name="addressFamily">An address family to use (available: <see cref="AddressFamily.InterNetwork"/> and <see cref="AddressFamily.InterNetworkV6"/>).</param>
|
||||||
|
/// <returns>The resolved <see cref="IPAddress"/>es or an empty list.</returns>
|
||||||
|
public static List<IPAddress> ResolveInterface(string interfaceName, AddressFamily addressFamily = default)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrWhiteSpace(interfaceName))
|
||||||
|
return new();
|
||||||
|
|
||||||
|
if (addressFamily != AddressFamily.InterNetwork && addressFamily != AddressFamily.InterNetworkV6)
|
||||||
|
addressFamily = AddressFamily.Unspecified;
|
||||||
|
|
||||||
|
var ipAddress = ResolveIpAddress(interfaceName, addressFamily);
|
||||||
|
// the name was an ip address, should not happen but experience tells other stories
|
||||||
|
if (ipAddress != null)
|
||||||
|
return new() { ipAddress };
|
||||||
|
|
||||||
|
try
|
||||||
{
|
{
|
||||||
return NetworkInterface.GetAllNetworkInterfaces()
|
return NetworkInterface.GetAllNetworkInterfaces()
|
||||||
.Where(nic => nic.Name.Equals(iface, StringComparison.OrdinalIgnoreCase))
|
.Where(nic => nic.Name.Equals(interfaceName, StringComparison.OrdinalIgnoreCase))
|
||||||
.SelectMany(nic => nic.GetIPProperties().UnicastAddresses.Select(ai => ai.Address))
|
.SelectMany(nic => nic.GetIPProperties().UnicastAddresses.Select(uai => uai.Address))
|
||||||
.Where(a => a.AddressFamily == AddressFamily.InterNetwork || a.AddressFamily == AddressFamily.InterNetworkV6)
|
.FilterAddressFamily(addressFamily)
|
||||||
.Where(a => addressFamily == AddressFamily.Unspecified || a.AddressFamily == addressFamily)
|
.ToList();
|
||||||
.OrderBy(a => a.AddressFamily)
|
|
||||||
.FirstOrDefault() ?? fallback;
|
|
||||||
}
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
return new();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static IPAddress ResolveIpAddress(string address, AddressFamily addressFamily)
|
||||||
|
{
|
||||||
|
if (IPAddress.TryParse(address, out var ipAddress))
|
||||||
|
{
|
||||||
|
// the address is whether IPv4 nor IPv6
|
||||||
|
if (ipAddress.AddressFamily != AddressFamily.InterNetwork && ipAddress.AddressFamily != AddressFamily.InterNetworkV6)
|
||||||
|
return null;
|
||||||
|
|
||||||
|
// the address does not match the required address family
|
||||||
|
if (addressFamily != AddressFamily.Unspecified && ipAddress.AddressFamily != addressFamily)
|
||||||
|
return null;
|
||||||
|
|
||||||
|
return ipAddress;
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static IEnumerable<IPAddress> FilterAddressFamily(this IEnumerable<IPAddress> source, AddressFamily addressFamily)
|
||||||
|
{
|
||||||
|
return source
|
||||||
|
.Where(a => a.AddressFamily == AddressFamily.InterNetwork || a.AddressFamily == AddressFamily.InterNetworkV6)
|
||||||
|
.Where(a => addressFamily == AddressFamily.Unspecified || a.AddressFamily == addressFamily)
|
||||||
|
.OrderBy(a => a.AddressFamily);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,11 +3,11 @@ Microsoft Visual Studio Solution File, Format Version 12.00
|
|||||||
# Visual Studio Version 16
|
# Visual Studio Version 16
|
||||||
VisualStudioVersion = 16.0.31729.503
|
VisualStudioVersion = 16.0.31729.503
|
||||||
MinimumVisualStudioVersion = 10.0.40219.1
|
MinimumVisualStudioVersion = 10.0.40219.1
|
||||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AMWD.Common", "AMWD.Common\AMWD.Common.csproj", "{F512C474-B670-4E47-911E-7C0674AA8E7E}"
|
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "AMWD.Common", "AMWD.Common\AMWD.Common.csproj", "{F512C474-B670-4E47-911E-7C0674AA8E7E}"
|
||||||
EndProject
|
EndProject
|
||||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AMWD.Common.AspNetCore", "AMWD.Common.AspNetCore\AMWD.Common.AspNetCore.csproj", "{725F40C9-8172-487F-B3D0-D7E38B4DB197}"
|
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "AMWD.Common.AspNetCore", "AMWD.Common.AspNetCore\AMWD.Common.AspNetCore.csproj", "{725F40C9-8172-487F-B3D0-D7E38B4DB197}"
|
||||||
EndProject
|
EndProject
|
||||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AMWD.Common.EntityFrameworkCore", "AMWD.Common.EntityFrameworkCore\AMWD.Common.EntityFrameworkCore.csproj", "{7091CECF-C981-4FB9-9CC6-91C4E65A6356}"
|
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "AMWD.Common.EntityFrameworkCore", "AMWD.Common.EntityFrameworkCore\AMWD.Common.EntityFrameworkCore.csproj", "{7091CECF-C981-4FB9-9CC6-91C4E65A6356}"
|
||||||
EndProject
|
EndProject
|
||||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{AFBF83AE-FE7D-48C1-B7E7-31BF3E17C6FB}"
|
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{AFBF83AE-FE7D-48C1-B7E7-31BF3E17C6FB}"
|
||||||
ProjectSection(SolutionItems) = preProject
|
ProjectSection(SolutionItems) = preProject
|
||||||
@@ -15,6 +15,9 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution
|
|||||||
.gitignore = .gitignore
|
.gitignore = .gitignore
|
||||||
.gitlab-ci.yml = .gitlab-ci.yml
|
.gitlab-ci.yml = .gitlab-ci.yml
|
||||||
CodeMaid.config = CodeMaid.config
|
CodeMaid.config = CodeMaid.config
|
||||||
|
icon.png = icon.png
|
||||||
|
LICENSE.txt = LICENSE.txt
|
||||||
|
README.md = README.md
|
||||||
EndProjectSection
|
EndProjectSection
|
||||||
EndProject
|
EndProject
|
||||||
Global
|
Global
|
||||||
|
|||||||
21
LICENSE.txt
Normal file
21
LICENSE.txt
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
The MIT License
|
||||||
|
|
||||||
|
Copyright (c) 2020-2021 Andreas Müller
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in
|
||||||
|
all copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||||
|
THE SOFTWARE.
|
||||||
20
README.md
Normal file
20
README.md
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
# Common
|
||||||
|
|
||||||
|
This is a collection of commonly used extensions, etc. on my projects.
|
||||||
|
To save time, they are all packed to NuGet packages.
|
||||||
|
|
||||||
|
## Documentation
|
||||||
|
|
||||||
|
Documentation can be found here: [AM.WD Wiki](https://wiki.am-wd.de/libs/common)
|
||||||
|
|
||||||
|
## Use in projects
|
||||||
|
|
||||||
|
Create a `nuget.config` file in your root project folder (where the `.sln` file is located)
|
||||||
|
```
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<configuration>
|
||||||
|
<packageSources>
|
||||||
|
<add key="AM.WD" value="https://nuget.am-wd.de/v3/index.json" />
|
||||||
|
</packageSources>
|
||||||
|
</configuration>
|
||||||
|
```
|
||||||
Reference in New Issue
Block a user