Skip to content

Instantly share code, notes, and snippets.

@mgroves
Created May 10, 2014 03:15
Show Gist options
  • Select an option

  • Save mgroves/148bf35335e5633c2884 to your computer and use it in GitHub Desktop.

Select an option

Save mgroves/148bf35335e5633c2884 to your computer and use it in GitHub Desktop.
[Serializable]
[MulticastAttributeUsage(MulticastTargets.Method, TargetMemberAttributes = MulticastAttributes.Public)]
public class ServiceErrorInterceptor : MethodInterceptionAspect
{
[NonSerialized]
ServiceErrorConcern _concern;
public static bool On = true;
public override bool CompileTimeValidate(MethodBase method)
{
if (!typeof (ServiceBase).IsAssignableFrom(method.DeclaringType))
{
var declaringType = method.DeclaringType;
var declaringTypeName = "Unknown";
if (declaringType != null)
declaringTypeName = declaringType.FullName;
Message.Write(method, SeverityType.Error, "SEI01", "The target type (" + declaringTypeName + ") does not implement ServiceBase.");
return false;
}
return base.CompileTimeValidate(method);
}
public override void RuntimeInitialize(MethodBase method)
{
if (!On) return;
// I'm newing up directly instead of using StructureMap/IoC
// because my project structure would require some extra hoops
// but I plan to do that work when it makes sense
// so think of this as a technical debt decision
_concern = new ServiceErrorConcern(new ExceptionlessLogger());
}
public override void OnInvoke(MethodInterceptionArgs args)
{
if (!On)
{
args.Proceed();
return;
}
_concern.OnInvoke(new MethodConcernArgs(args));
}
}
public interface IMethodConcernArgs
{
void Proceed();
object Instance { get; }
MethodBase Method { get; }
object ReturnValue { get; set; }
}
public class MethodConcernArgs : IMethodConcernArgs
{
readonly MethodInterceptionArgs _args;
public MethodConcernArgs(MethodInterceptionArgs args)
{
_args = args;
}
public void Proceed()
{
_args.Proceed();
}
public object Instance
{
get { return _args.Instance; }
}
public MethodBase Method
{
get { return _args.Method; }
}
public object ReturnValue
{
get { return _args.ReturnValue; }
set { _args.ReturnValue = value; }
}
}
public class ServiceErrorConcern
{
readonly IExceptionLogger _exceptionLogger;
public ServiceErrorConcern(IExceptionLogger exceptionLogger)
{
_exceptionLogger = exceptionLogger;
}
public void OnInvoke(IMethodConcernArgs args)
{
try
{
args.Proceed();
}
catch (SqlException ex)
{
var obj = (ServiceBase)args.Instance;
if (ex.Message.Contains("The DELETE statement conflicted with the REFERENCE constraint"))
obj.AddValidationError("This item can't be deleted because it is still used by other items.");
else
{
obj.AddValidationError(GetErrorMessage(ex));
_exceptionLogger.Log(ex);
}
ErrorReturn(args);
}
catch (NotImplementedException)
{
// if something's not implemented, just throw it up
throw;
}
catch (Exception ex)
{
_exceptionLogger.Log(ex);
var obj = (ServiceBase)args.Instance;
obj.AddValidationError(GetErrorMessage(ex));
ErrorReturn(args);
}
}
string GetErrorMessage(Exception ex)
{
if (ex is SqlException)
{
if (IsThisSite.RunningLocally)
return "There was a database error. Please try again later. (" + ex.Message + "), (" + ex.GetType().FullName + ")";
return "There was a database error. Please try again later.";
}
if (IsThisSite.RunningLocally)
return "There was an error. Please try again later. (" + ex.Message + "), (" + ex.GetType().FullName + ")";
return "There was an error. Please try again later.";
}
// if the return type is ienumerable, then I want the return type to be an empty collection
// if the return type is value, then I want the return to be default value
// if the return type is void, don't touch the return value
// if the return type is reference (should cover everything else), then return should be null
void ErrorReturn(IMethodConcernArgs args)
{
try
{
var methodInfo = args.Method as MethodInfo;
if (methodInfo == null)
return;
if (methodInfo.ReturnType == typeof(string))
args.ReturnValue = null;
else if (typeof(IEnumerable).IsAssignableFrom(methodInfo.ReturnType))
args.ReturnValue = Activator.CreateInstance(methodInfo.ReturnType);
else if (methodInfo.ReturnType.IsValueType)
args.ReturnValue = Activator.CreateInstance(methodInfo.ReturnType);
else if (methodInfo.ReturnType != typeof (void))
args.ReturnValue = null;
}
catch
{
// don't want any errors to take place trying to build the correct return value
// so just swallow them, and let args.ReturnValue stay whatever it is
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment