Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Is there a way to trap all errors in a AJAX-web service?

I'd like to trap any unhandled exception thrown in an ASP.NET web service, but nothing I've tried has worked so far.

First off, the HttpApplication.Error event doesn't fire on web services, so that's out..

The next approach was to implement a soap extension, and add it to web.config with:

<soapExtensionTypes>
   <add type="Foo" priority="1" group="0" />
</soapExtensionTypes>

However, this doesn't work if you call the web method over JSON (which my web site does exclusively)..

My next idea would be to write my own HttpHandler for .asmx, which would hopefully derive from System.Web.Script.Services.ScriptHandlerFactory and do something smart. I haven't tried this yet.

Is there an approach I'm missing? Thanks!

Mike

UPDATE:

I'll summarize the possibly solutions here:

1) Upgrade to WCF which makes this whole thing much, much easier.

2) Since you cannot sub-class or override the RestHandler class, you would have to re-implement the whole thing as your own IHttpHandler or use reflection to manually call into its methods. Since the source to RestHandler is public and only about 500 lines long, making your own version might not be a huge amount of work but you'd then be responsible for maintaining it. I'm also unaware of any licensing restrictions involved with this code.

3) You can wrap your methods in try/catch blocks, or perhaps use LAMBDA expressions to make this code a bit cleaner. It would still require you to modify each method in your web service.

like image 932
Mike Christensen Avatar asked Aug 28 '10 01:08

Mike Christensen


Video Answer


2 Answers

As described in Capture all unhandled exceptions automatically with WebService there really is no good solution.

The reason that you cannot capture the HttpApplication.Error etc has to do with how the RestHandler has been implemented by the good folks at Microsoft. Specifically, the RestHandler explicitly catches (handles) the exception and writes out the exception details to the Response:

internal static void ExecuteWebServiceCall(HttpContext context, WebServiceMethodData methodData)
{
    try
    {
        NamedPermissionSet namedPermissionSet = HttpRuntime.NamedPermissionSet;
        if (namedPermissionSet != null)
        {
            namedPermissionSet.PermitOnly();
        }
        IDictionary<string, object> rawParams = GetRawParams(methodData, context);
        InvokeMethod(context, methodData, rawParams);
    }
    catch (Exception exception)
    {
        WriteExceptionJsonString(context, exception);
    }
}

To make matters worse, there is no clean extension point (that I could find) where you can change/extend the behavior. If you want to go down the path of writing your own IHttpHandler, I believe you will pretty much have to re-implement the RestHandler (or RestHandlerWithSession); regardless Reflector will be your friend.

For those that may choose to modify their WebMethods

If you are using Visual Studio 2008 or later, using Lambda expressions makes things not too bad (although not global/generic solution) in terms or removing duplicated code.

[WebMethod]
[ScriptMethod(UseHttpGet = true, ResponseFormat = ResponseFormat.Json)]
public String GetServerTime()
{
  return Execute(() => DateTime.Now.ToString());
}

public T Execute<T>(Func<T> action)
{
  if (action == null)
    throw new ArgumentNullException("action");

  try
  {
    return action.Invoke();
  }
  catch (Exception ex)
  {
    throw; // Do meaningful error handling/logging...
  }
}

Where Execute can be implemented in a subclass of WebService or as an extension method.

UPDATE: Reflection Evil

As mentioned in my origional answer, you can abuse reflection to get what you want... specifically you can create your own HttpHandler that makes use of the internals of the RestHandler to provide an interception point for capturing exception details. I have include an "unsafe" code example below to get you started.

Personally, I would NOT use this code; but it works.

namespace WebHackery
{
  public class AjaxServiceHandler : IHttpHandler
  {
    private readonly Type _restHandlerType;
    private readonly MethodInfo _createHandler;
    private readonly MethodInfo _getRawParams;
    private readonly MethodInfo _invokeMethod;
    private readonly MethodInfo _writeExceptionJsonString;
    private readonly FieldInfo _webServiceMethodData;

    public AjaxServiceHandler()
    {
      _restHandlerType = typeof(ScriptMethodAttribute).Assembly.GetType("System.Web.Script.Services.RestHandler");

      _createHandler = _restHandlerType.GetMethod("CreateHandler", BindingFlags.NonPublic | BindingFlags.Static, null, new[] { typeof(HttpContext) }, null);
      _getRawParams = _restHandlerType.GetMethod("GetRawParams", BindingFlags.NonPublic | BindingFlags.Static);
      _invokeMethod = _restHandlerType.GetMethod("InvokeMethod", BindingFlags.NonPublic | BindingFlags.Static);
      _writeExceptionJsonString = _restHandlerType.GetMethod("WriteExceptionJsonString", BindingFlags.NonPublic | BindingFlags.Static, null, new[] { typeof(HttpContext), typeof(Exception) }, null);

      _webServiceMethodData = _restHandlerType.GetField("_webServiceMethodData", BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.GetField);
    }

    public bool IsReusable
    {
      get { return true; }
    }

    public void ProcessRequest(HttpContext context)
    {
      var restHandler = _createHandler.Invoke(null, new Object[] { context });
      var methodData = _webServiceMethodData.GetValue(restHandler);
      var rawParams = _getRawParams.Invoke(null, new[] { methodData, context });

      try
      {
        _invokeMethod.Invoke(null, new[] { context, methodData, rawParams });
      }
      catch (Exception ex)
      {
        while (ex is TargetInvocationException)
          ex = ex.InnerException;

        // Insert Custom Error Handling HERE...

        _writeExceptionJsonString.Invoke(null, new Object[] { context, ex});
      }
    }
  }
}
like image 87
Chris Baxter Avatar answered Oct 15 '22 02:10

Chris Baxter


This a good question. I've had this problem in the web app that I work on. I'm afraid my solution wasn't smart in any way. I simply wrapped in the code in each web service method in a try/catch and returned an object which indicated whether the method had run successfully or not. Fortunately my app doesn't have a massive number of web service calls so I can get away will this. I appreciate it's not a good solution if you're doing loads of web service calls.

like image 44
Phil Hale Avatar answered Oct 15 '22 03:10

Phil Hale