我想捕获ASP.NET Web服务中抛出的任何未处理的异常,但到目前为止我还没有尝试过。
首先,HttpApplication.Error事件不会在Web服务上触发,所以就这样了..
下一个方法是实现soap扩展,并使用:
将其添加到web.config<soapExtensionTypes>
<add type="Foo" priority="1" group="0" />
</soapExtensionTypes>
但是,如果您通过JSON(我的网站专门执行)调用Web方法,则此方法无效。
我的下一个想法是为.asmx编写自己的HttpHandler,它希望从System.Web.Script.Services.ScriptHandlerFactory派生出来并做一些聪明的事情。我还没试过这个。
我缺少一种方法吗?谢谢!
麦克
更新
我将在此总结可能的解决方案:
1)升级到WCF,这使整个事情变得更加容易。
2)由于您不能对RestHandler类进行子类化或覆盖,因此您必须将整个事物重新实现为您自己的IHttpHandler或使用反射手动调用其方法。由于RestHandler的源代码是公共的,只有大约500行,因此制作您自己的版本可能不是很大的工作量,但您需要负责维护它。我也没有意识到此代码涉及的任何许可限制。
3)您可以将方法包装在try / catch块中,或者使用LAMBDA表达式使此代码更清晰一些。它仍然需要您修改Web服务中的每个方法。
答案 0 :(得分:11)
如Capture all unhandled exceptions automatically with WebService所述,确实没有好的解决方案。
您无法捕获HttpApplication.Error等的原因与Microsoft的优秀人员如何实现RestHandler有关。具体来说,RestHandler显式捕获(处理)异常并将异常详细信息写出到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);
}
}
更糟糕的是,没有干净的扩展点(我可以找到),您可以在其中更改/扩展行为。如果你想要编写自己的IHttpHandler,我相信你几乎必须重新实现RestHandler(或RestHandlerWithSession);无论Reflector将成为你的朋友。
对于那些可能选择修改其WebMethods的人
如果您使用的是Visual Studio 2008或更高版本,那么使用Lambda表达式会使术语变得太糟糕(尽管不是全局/通用解决方案)或删除重复的代码。
[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});
}
}
}
}
答案 1 :(得分:4)
这个问题很好。我在我工作的网络应用程序中遇到了这个问题。我担心我的解决方案在任何方面都不聪明。我只是在try / catch中的每个Web服务方法中包含代码,并返回一个对象,该对象指示方法是否已成功运行。幸运的是,我的应用程序没有大量的Web服务调用,所以我可以逃脱这个。如果您正在进行大量的Web服务调用,我感谢它不是一个好的解决方案。
答案 2 :(得分:2)
你看过WCF Web API了吗? 目前处于测试阶段,但已经在多个实时网站中使用。 公开json / xml rest api很容易。 在其中,您可以创建自己的HttpErrorHandler派生类来处理错误。这是一个非常干净的解决方案。可能值得仔细看看你。迁移任何现有服务也应该是直截了当的。希望它有所帮助!
答案 3 :(得分:2)
修改强>
您是否尝试在web.config中关闭错误处理,然后添加错误模块?
<location path="Webservices" >
<system.web>
<customErrors mode="Off" />
</system.web>
</location>
正常的错误处理,因为我认为它应该适用于RestHandler
您可以创建一个HttpModule来捕获所有错误并返回自定义结果。请注意,这将是错误消息将是对客户端的响应,而不是来自Web服务的预期结果。您还可以在应用程序中抛出任何自定义异常,如果客户端要响应或通知用户,则让模块处理它。
正确执行的示例结果:
{
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响应(您需要使用自己的json错误消息实现替换html内容):
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();
}
}
}