Есть ли способ отловить все ошибки в AJAX-веб-сервисе? - PullRequest
15 голосов
/ 28 августа 2010

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

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

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

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

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

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

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

Майк

ОБНОВЛЕНИЕ:

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

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

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

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

Ответы [ 4 ]

11 голосов
/ 16 ноября 2011

Как описано в Автоматический захват всех необработанных исключений с помощью 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); Отражатель будет твоим другом.

Для тех, кто может изменить свои 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 или как метод расширения.

ОБНОВЛЕНИЕ: Отражение зла

Как уже упоминалось в моем первоначальном ответе, вы можете злоупотреблять рефлексией, чтобы получить то, что вы хотите ... в частности, вы можете создать свой собственный 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});
      }
    }
  }
}
4 голосов
/ 22 февраля 2011

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

2 голосов
/ 17 ноября 2011

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

Вы пытались отключить обработку ошибок в 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();
        }
    }
}
2 голосов
/ 16 ноября 2011

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...