2010-08-28 3 views
15

Я хотел бы уловить любое необработанное исключение, брошенное в веб-службу ASP.NET, но ничто из того, что я пробовал, пока не работает.Есть ли способ уловить все ошибки в веб-службе AJAX?

Во-первых, событие HttpApplication.Error не срабатывает на веб-сервисов, так что из ..

Следующий подход был реализовать расширение мыла и добавить его в web.config с:

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

Однако, это не работает, если вы звоните в веб-метод через JSON (который мой веб-сайт делает исключительно) ..

Моя следующая идея будет написать свою собственную HttpHandler для .asmx, который бы надеюсь, выйдут из System.Web.Script.Services.ScriptHandlerFac и сделать что-то умное. Я еще не пробовал.

Есть ли подход, который мне не хватает? Благодаря!

Майк

UPDATE:

Я Резюмируя возможно решение здесь:

1) Обновление до WCF, что делает все это дело намного проще.

2) Поскольку вы не можете подклассифицировать или переопределить класс RestHandler, вам придется повторно реализовать все это как свой собственный IHttpHandler или использовать отражение, чтобы вручную вызвать его методы. Поскольку источник для RestHandler является общедоступным и составляет всего около 500 строк, что делает вашу собственную версию не большой работой, но тогда вы будете нести ответственность за ее сохранение. Я также не знаю каких-либо лицензионных ограничений, связанных с этим кодом.

3) Вы можете обернуть свои методы в блоки try/catch или, возможно, использовать выражения LAMBDA, чтобы сделать этот код немного более чистым. Вам все равно потребуется изменить каждый метод в вашем веб-сервисе.

+0

Есть ли гибкость в требование о том, что вы не можете изменять веб-методы? –

+0

Ну, да - пока мне не нужно окружать все их в гигантских блоках try/catch. Если решение чистое, то я открыт для него. –

+0

У меня есть решение, для которого требуется блок Try и одна строка Catch. Вы можете улучшить его, чтобы полностью избавиться от попытки/улова, я не вырыл это глубоко. Хотите опубликовать? –

ответ

11

Как описано в Capture all unhandled exceptions automatically with WebService, на самом деле нет хорошего решения.

Причина, по которой вы не можете захватить HttpApplication.Error и т. Д., Связана с тем, как RestHandler был реализован хорошими людьми в Microsoft. В частности, RestHandler явно ловит (ручки) исключение и выписывает детали исключения в ответ:

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); 
    } 
} 

В довершение, нет чистой точки расширения (что я мог бы найти), где вы можете изменить/расширить поведение. Если вы хотите пойти по пути написания собственного IHttpHandler, я верю, что вам придется переустановить RestHandler (или RestHandlerWithSession); независимо от Reflector будет вашим другом.

Для тех, которые могут выбрать, чтобы изменить их WebMethods

Если вы используете Visual Studio 2008 или более поздней версии, с помощью лямбда-выражений делает вещи не так уж плохо (хотя и не глобальный/общее решение) в терминах или удаление дублируется код.

[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... 
    } 
} 

Где Execute может быть реализовано в подклассе WebService или в качестве метода расширения.

UPDATE: Отражение Зла

Как упоминалось в моем оригинальный ответ, вы можете злоупотреблять отражение, чтобы получить то, что вы хотите ... конкретно вы можете создать свой собственный HttpHandler, который использует внутренностями RestHandler для предоставить точку перехвата для получения сведений об исключении. У меня есть пример «небезопасного» кода ниже, чтобы вы начали.

Лично я бы не использовал этот код; но он работает.

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}); 
     } 
    } 
    } 
} 
+0

Да, я думаю, что я мог бы изучить код и посмотреть, могу ли я написать HttpHandler, который каким-то образом «обертывает» существующий обработчик. Кроме этого, я думаю, что нет чистого решения, и я должен просто добавить обработку ошибок к более сложным веб-методам. +1 для вашего подробного ответа! –

+0

Из-за того, что RestHandler был реализован, единственным вариантом для расширения, вероятно, будет злоупотребление Reflection (при условии полного доверия), поскольку все в RestHandler является внутренним или частным. –

+0

Eh go figure .. Интересно, можете ли вы написать какой-то HttpFilter, который обнаруживает исключения SOAP в потоке ответов и записывает их в базу данных. На самом деле все, что я хочу сделать, это регистрация ошибок. Прямо сейчас я задерживаю исключения в Javascript, а затем повторно вызываю другой веб-метод для регистрации ошибки. –

4

Это хороший вопрос. У меня была эта проблема в веб-приложении, над которым я работаю. Боюсь, что мое решение не было таким уж умным. Я просто обернул код в каждом методе веб-службы в try/catch и возвратил объект, который указывал, был ли метод успешно запущен или нет. К счастью, у моего приложения нет большого количества вызовов веб-сервисов, поэтому я могу уйти от этого. Я ценю, что это нехорошее решение, если вы выполняете множество вызовов веб-сервисов.

+0

Я делаю то же самое в последнем веб-приложении, над которым я работаю. У меня есть много вызовов ajax через jquery, и когда метод на стороне сервера вызывает исключение, я его поймаю, наряжаю JSON и получаю глобальный метод на стороне клиента, который уведомляет пользователя о возникшей проблеме и о том, какие детали этой проблемы были. –

2

Вы взглянули на WCF Web API? Это в настоящее время находится в стадии разработки, но уже используется в нескольких сайтах. Выявление json/xml rest api's очень просто. В нем вы можете создать свой собственный производный класс HttpErrorHandler для обработки ошибок. Это очень чистое решение. Возможно, стоит поближе познакомиться с вами. Миграция любого существующего сервиса также должна быть простой. Надеюсь, поможет!

+0

Это очень интересно! Я не думаю, что пока буду использовать его в производстве, но я обязательно буду следить за этим. +1 за ссылку! –

+1

Постараюсь, что это намного проще в мире WCF; просто создайте IErrorHandler (http://msdn.microsoft.com/en-us/library/system.servicemodel.dispatcher.ierrorhandler(v=vs.85).aspx) и связанное с ним поведение службы, и все готово. –

+0

Да, похоже, моя лучшая ставка в долгосрочной перспективе - переносить мой код на WCF. Интересно, как это будет тяжело. –

2

Редактировать

попытке включить обработку ошибки выключения в web.config, а затем добавить модуль ошибки ли?

<location path="Webservices" > 
    <system.web> 
    <customErrors mode="Off" /> 
    </system.web> 
</location> 

Нормальная обработка ошибок, как я думаю, что это должно быть даже для RestHandler

Вы можете создать HttpModule поймать все ошибки и вернуть пользовательский результат. Обратите внимание, что это сообщение об ошибке будет ответом клиенту, а не ожидаемым результатом веб-службы. Вы также можете выбросить исключение custom в приложении и позволить модулю обрабатывать его, если клиент должен ответить или уведомить пользователя об этом.

Пример результата правильного исполнения:

{ 
    success: true, 
    data: [ .... ], 
    error: null 
} 

Пример результат неудачного выполнения:

{ 
    success: false, 
    data: null, 
    error: { 
    message: 'error message', 
    stackTrace: 'url encoded stack trace' 
    } 
} 

Добавьте к этому web.config:

<modules> 
    <add name="UnhandledException" type="Modules.Log.UnhandledException"/> 
</modules> 

Вы можете легко добавить код сделать модуль саморегистрацией, реализовав довольно недокументированный PreApplicationStartMethod функционирует в модуле и создает и использует ваш собственный класс HttpApplication вместо стандартного.Делая это, вы можете добавить любой модуль в приложение, просто добавив его в папку bin приложения. Это то, как я делаю все свои приложения в настоящее время, и я должен сказать, что он работает отлично.

Ниже код будет отлавливать все ошибки приложения и возвращать HTML ответ с сообщением об ошибке (вам нужно заменить HTML вещь с вашей собственной реализацией сообщений об ошибке JSON):

using System.Web; 

namespace Modules.Log 
{ 
    using System; 
    using System.Text; 

    public class LogModule : IHttpModule 
    { 
     private bool initialized = false; 
     private object initLock = new Object(); 
     private static string applicationName = string.Format("Server: {0}; [LogModule_AppName] installation name is not set", System.Environment.MachineName); 

     public void Dispose() 
     { 
     } 

     public void Init(HttpApplication context) 
     { 
      // Do this one time for each AppDomain. 
      if (!initialized) 
      { 
       lock (initLock) 
       { 
        if (!initialized) 
        { 
         context.Error += new EventHandler(this.OnUnhandledApplicationException); 

         initialized = true; 
        } 
       } 
      } 
     } 

     private void OnUnhandledApplicationException(object sender, EventArgs e) 
     { 
      StringBuilder message = new StringBuilder("<html><head><style>" + 
       "body, table { font-size: 12px; font-family: Arial, sans-serif; }\r\n" + 
       "table tr td { padding: 4px; }\r\n" + 
       ".header { font-weight: 900; font-size: 14px; color: #fff; background-color: #2b4e74; }\r\n" + 
       ".header2 { font-weight: 900; background-color: #c0c0c0; }\r\n" + 
       "</style></head><body><table><tr><td class=\"header\">\r\n\r\nUnhandled Exception logged by LogModule.dll:\r\n\r\nappId="); 

      string appId = (string)AppDomain.CurrentDomain.GetData(".appId"); 
      if (appId != null) 
      { 
       message.Append(appId); 
      } 

      message.Append("</td></tr>"); 

      HttpServerUtility server = HttpContext.Current.Server; 
      Exception currentException = server.GetLastError(); 

      if (currentException != null) 
      { 
       message.AppendFormat(
         "<tr><td class=\"header2\">TYPE</td></tr><tr><td>{0}</td></tr><tr><td class=\"header2\">REQUEST</td></tr><tr><td>{3}</td></tr><tr><td class=\"header2\">MESSAGE</td></tr><tr><td>{1}</td></tr><tr><td class=\"header2\">STACK TRACE</td></tr><tr><td>{2}</td></tr>", 
         currentException.GetType().FullName, 
         currentException.Message, 
         currentException.StackTrace, 
         HttpContext.Current != null ? HttpContext.Current.Request.FilePath : "n/a"); 
       server.ClearError(); 
      } 

      message.Append("</table></body></html>"); 

      HttpContext.Current.Response.Write(message.ToString()); 
      server.ClearError(); 
     } 
    } 
} 
+0

Ajax вызывает веб-методы, не бросает необработанные исключения (см. Первый фрагмент кода в моем ответе).Этот подход работает для регулярных вызовов, но RestHandler удаляет это как опцию, поскольку исключение «обрабатывается». –

+0

Ах, пропустил это ... обновил сообщение с другой вещью, которая может сработать ... – Asken

+0

Я думаю, что @CalgaryCoder подводит итог довольно хорошо - Исключения пойманы, а затем вызывается WriteExceptionJsonString', чтобы выписать сериализованную информацию об исключении как JSON строка. Вам придется перехватить это, чтобы иметь какую-то удачу. Это хромало, что они не делали эти классы расширяемыми, это было бы довольно прямолинейные изменения дизайна, чтобы добавить мир большей мощности. –

Смежные вопросы