Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Exception handling middleware #25

Open
wants to merge 4 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 25 additions & 0 deletions src/WaCore.Common/ExceptionHandling/HandlingInput.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
using System;

namespace WaCore.Common.ExceptionHandling
{
public class HandlingInput<TParameter>
{
public Exception Exception { get; }

public TParameter Parameter { get; }

public HandlingInput(Exception exception, TParameter parameter = default(TParameter))
{
Exception = exception ?? throw new ArgumentNullException(nameof(exception));
Parameter = parameter;
}
}

public class HandlingInput : HandlingInput<object>
{
public HandlingInput(Exception exception, object parameter = null) : base(exception, parameter)
{

}
}
}
8 changes: 8 additions & 0 deletions src/WaCore.Common/ExceptionHandling/HandlingMatchType.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
namespace WaCore.Common.ExceptionHandling
{
public enum HandlingMatchType
{
Inheritance,
ExactType
}
}
31 changes: 31 additions & 0 deletions src/WaCore.Common/ExceptionHandling/HandlingResult.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
namespace WaCore.Common.ExceptionHandling
{
public class HandlingResult<TResult>
{
public HandlingResult()
{
Handled = true;
}

public HandlingResult(bool handled, TResult result = default(TResult))
{
Handled = handled;
Result = result;
}

public bool Handled { get; }

public TResult Result { get; }
}

public class HandlingResult : HandlingResult<object>
{
public HandlingResult()
{
}

public HandlingResult(bool handled, object result = null) : base(handled, result)
{
}
}
}
15 changes: 15 additions & 0 deletions src/WaCore.Common/ExceptionHandling/NoHandlerMatchedException.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
using System;

namespace WaCore.Common.ExceptionHandling
{
public class NoHandlerMatchedException: Exception
{
private const string DefaultMessage = "No handler matched the exception to catch";

public NoHandlerMatchedException(Exception innerException)
: base(DefaultMessage, innerException)
{

}
}
}
65 changes: 65 additions & 0 deletions src/WaCore.Common/ExceptionHandling/WacHandling.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
using System;

namespace WaCore.Common.ExceptionHandling
{
public static class WacHandling
{
private static readonly HandlingResult<object> HandledResult = new HandlingResult(true);

private static readonly HandlingResult<object> IgnoredResult = new HandlingResult(false);

public static WacHandlingConfiguration<TParameter, TResult> Prepare<TParameter, TResult>()
{
return new WacHandlingConfiguration<TParameter, TResult>();
}

public static WacHandlingConfiguration Prepare()
{
return new WacHandlingConfiguration();
}

public static TResult Result<TResult>(this HandlingResult<object> result)
{
if (result == null) throw new ArgumentNullException(nameof(result));

return (TResult)result.Result;
}

public static TResult Parameter<TResult>(this HandlingInput<object> input)
{
if (input == null) throw new ArgumentNullException(nameof(input));

return (TResult)input.Parameter;
}

public static HandlingResult<TResult> Handled<TResult>()
{
return new HandlingResult<TResult>(true);
}

public static HandlingResult<TResult> Handled<TResult>(TResult result)
{
return new HandlingResult<TResult>(true, result);
}

public static HandlingResult<object> Handled()
{
return HandledResult;
}

public static HandlingResult<object> Handled(object result)
{
return new HandlingResult(true, result);
}

public static HandlingResult<TResult> Ignore<TResult>()
{
return new HandlingResult<TResult>(false);
}

public static HandlingResult<object> Ignore()
{
return IgnoredResult;
}
}
}
237 changes: 237 additions & 0 deletions src/WaCore.Common/ExceptionHandling/WacHandlingConfiguration.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,237 @@
using System;
using System.Collections.Generic;
using System.Linq;

namespace WaCore.Common.ExceptionHandling
{
public class WacHandlingConfiguration<TParameter, TResult>
{
private readonly IList<Func<Exception, HandlingInput<TParameter>, HandlingResult<TResult>>> _handlers =
new List<Func<Exception, HandlingInput<TParameter>, HandlingResult<TResult>>>();

private Action<Exception, HandlingInput<TParameter>, HandlingResult<TResult>> _finalizationHandler;

public HandlingMatchType MatchType { get; set; } = HandlingMatchType.Inheritance;

private void AddHandler(Func<Exception, HandlingInput<TParameter>, HandlingResult<TResult>> handler)
{
_handlers.Add(handler);
}

private bool TryCastException<TException>(Exception sourceException, out TException exception) where TException: Exception
{
exception = null;
switch (MatchType)
{
case HandlingMatchType.Inheritance:
{
if (sourceException is TException)
{
exception = (TException) sourceException;
return true;
}
return false;
}
case HandlingMatchType.ExactType:
{
if (sourceException.GetType() == typeof(TException))
{
exception = (TException)sourceException;
return true;
}
return false;
}
default:
throw new NotImplementedException($"Unrecognized HandlingMatchType value - '{MatchType}'");
}
}

public bool ContainsHandler<TException>() where TException : Exception
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I noticed that this method seems to return always false, because handlerExceptionType is always of type Exception.
I guess, that it should return true, if you specify a WebApiException handler in the startup as follows:

                    .On<WebApiException>(e => WacHandling.Handled(new WebApiExceptionDto
                    {
                        Message = "test my own handling",
                        HttpStatusCode = (HttpStatusCode)0,
                        ErrorReference = Guid.NewGuid().ToString()
                    }))

{
foreach (var handler in _handlers)
{
var handlerType = handler.GetType();
var handlerExceptionType = handlerType.GetGenericArguments().First();

if (handlerExceptionType == typeof(TException))
{
return true;
}
}

return false;
}

public WacHandlingConfiguration<TParameter, TResult> On<TException>(
Action<TException, HandlingInput<TParameter>> handler,
Func<TException, HandlingInput<TParameter>, bool> condition = null)
where TException : Exception
{
if (handler == null) throw new ArgumentNullException(nameof(handler));

AddHandler((ex, i) =>
{
if (TryCastException(ex, out TException castedException))
{
if (condition == null || condition(castedException, i))
{
handler(castedException, i);
return new HandlingResult<TResult>(true);
}
}

return new HandlingResult<TResult>(false);
});

return this;
}

public WacHandlingConfiguration<TParameter, TResult> On<TException>(
Func<TException, HandlingInput<TParameter>, HandlingResult<TResult>> handler,
Func<TException, HandlingInput<TParameter>, bool> condition = null)
where TException : Exception
{
if (handler == null) throw new ArgumentNullException(nameof(handler));

AddHandler((ex, i) =>
{
if (TryCastException(ex, out TException castedException))
{
if (condition == null || condition(castedException, i))
{
return handler(castedException, i);
}
}

return new HandlingResult<TResult>(false);
});

return this;
}

public WacHandlingConfiguration<TParameter, TResult> On<TException>(
Action<TException> handler,
Func<TException, HandlingInput<TParameter>, bool> condition = null)
where TException : Exception
{
if (handler == null) throw new ArgumentNullException(nameof(handler));

AddHandler((ex, i) =>
{
if (TryCastException(ex, out TException castedException))
{
if (condition == null || condition(castedException, i))
{
handler(castedException);
return new HandlingResult<TResult>(true);
}
}

return new HandlingResult<TResult>(false);
});

return this;
}

public WacHandlingConfiguration<TParameter, TResult> On<TException>(
Func<TException, HandlingResult<TResult>> handler,
Func<TException, HandlingInput<TParameter>, bool> condition = null)
where TException : Exception
{
if (handler == null) throw new ArgumentNullException(nameof(handler));

AddHandler((ex, i) =>
{
if (TryCastException(ex, out TException castedException))
{
if (condition == null || condition(castedException, i))
{
return handler(castedException);
}
}

return new HandlingResult<TResult>(false);
});

return this;
}

public WacHandlingConfiguration<TParameter, TResult> FinalizeWith(
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I was wondering where you would implement Logging of exceptions.
Is FinalizeWith the method, where you would do that?
If not, what is an example, where this method would be used?

Action<Exception, HandlingInput<TParameter>, HandlingResult<TResult>> finalizationHandler)
{
_finalizationHandler = finalizationHandler ?? throw new ArgumentNullException(nameof(finalizationHandler));
return this;
}

public WacHandlingConfiguration<TParameter, TResult> FinalizeWith(
Action<Exception, HandlingInput<TParameter>> finalizationHandler)
{
if (finalizationHandler == null) throw new ArgumentNullException(nameof(finalizationHandler));

_finalizationHandler = (e, input, result) =>
{
finalizationHandler(e, input);
};
return this;
}

public WacHandlingConfiguration<TParameter, TResult> FinalizeWith(Action<Exception> finalizationHandler)
{
if (finalizationHandler == null) throw new ArgumentNullException(nameof(finalizationHandler));

_finalizationHandler = (e, input, result) =>
{
finalizationHandler(e);
};
return this;
}

public WacHandlingConfiguration<TParameter, TResult> FinalizeWith(Action finalizationHandler)
{
if (finalizationHandler == null) throw new ArgumentNullException(nameof(finalizationHandler));

_finalizationHandler = (e, input, result) =>
{
finalizationHandler();
};
return this;
}

public HandlingResult<TResult> Catch(
Exception exception, TParameter parameter = default(TParameter), bool throwIfNotHandled = true)
{
if (exception == null) throw new ArgumentNullException(nameof(exception));

var input = new HandlingInput<TParameter>(exception, parameter);
HandlingResult<TResult> result = null;
try
{
foreach (var handler in _handlers)
{
result = handler(exception, input);
if (result.Handled)
{
return result;
}
}

result = WacHandling.Ignore<TResult>();
if (throwIfNotHandled)
{
throw new NoHandlerMatchedException(exception);
}

return result;
}
finally
{
_finalizationHandler?.Invoke(exception, input, result ?? WacHandling.Ignore<TResult>());
}
}
}

public class WacHandlingConfiguration : WacHandlingConfiguration<object, object>
{

}
}
7 changes: 7 additions & 0 deletions src/WaCore.Common/WaCore.Common.csproj
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<TargetFramework>netstandard2.0</TargetFramework>
</PropertyGroup>

</Project>
Loading