Elmah 未在 MVC 应用程序中记录 http 发布请求的异常 - 如果请求包含 XML
Posted
技术标签:
【中文标题】Elmah 未在 MVC 应用程序中记录 http 发布请求的异常 - 如果请求包含 XML【英文标题】:Elmah not logging exceptions for http post requests in MVC app - if the request contains XML 【发布时间】:2012-07-22 17:53:46 【问题描述】:我在 MVC4 (RC) 应用程序中遇到了一个奇怪的问题。 (在 .NET 4.0 上运行)
我刚刚设置 Elmah 来记录异常/错误。
我基本上安装了 Elmah.MVC 和 elmah.sqlserver NuGet 包。 (分别为 2.0.0 和 1.2 版本)
开箱即用似乎运行良好 - 我可以转到 elmah 页面并查看错误:
http://myserver/elmah
例如,如果我创建了一些 404 错误,它们会出现在此日志中。
不起作用的是:我有一个标准 MVC 控制器,带有 [HttpPost]
操作。我已经设置好了,所以它总是会抛出异常:
public class TestController : Controller
[HttpPost]
[ValidateInput(false)]
public void Testing()
throw new Exception("uh oh");
然后我尝试通过 jQuery 将数据发布到这个控制器:
$.post('/Test/Testing', test_data: 'This is some test data');
好的,这行得通。响应返回典型的黄屏死机,错误被捕获并记录在 Elmah 中。
但是,如果我尝试发布 XML/html 之类的内容,则会出现 not 登录 Elmah 的错误。我仍然从服务器返回相同的响应(黄屏死机),但在 Elmah 中什么也没有。
$.post('/Test/Testing', test_data: '<test><test1>This is some test data</test1></test>');
为什么?这没有意义。
请注意,我已经关闭了对操作的请求验证。如果我不这样做,那么发布 XML/HTML 数据会导致此异常:
从客户端检测到有潜在危险的 Request.Form 值
NuGet 也会拒绝记录该异常 - 我认为这是一个错误:
http://code.google.com/p/elmah/issues/detail?id=217
那么,我遇到这个问题的原因是什么?它是与我在上面发现的问题相关的错误吗?
仅仅因为请求包含 XML/HTML,我无法记录异常,这似乎是一种非常不幸的情况。
肯定有办法解决这个问题吗?
【问题讨论】:
我在 Elmah 源中找到了这个,因为我遇到了同样的问题。如果通过模型(MVC)或控件(webforms)访问数据,没问题;但是如果你访问 request.Form["inputId"] (Elmah 在构建一个对象时所做的),它每次都会抛出一个异常。 这个问题解决了吗?如果我们的控制器的 Action 方法接受 HTML 数据(例如,来自富文本字段),则无法使用 Elmah 记录错误,这很愚蠢。 【参考方案1】:默认情况下,ELMAH 不会捕获 HttpRequestValidationException,如果用户发送无效请求,ELMAH 的报告中将忽略该请求。所以也有必要定义和使用这个全局过滤器:
public class ElmahRequestValidationErrorFilter : IExceptionFilter
public void OnException(ExceptionContext context)
if (context.Exception is HttpRequestValidationException)
ErrorLog.GetDefault(HttpContext.Current).Log(new Error(context.Exception));
【讨论】:
我们不会尝试捕获无效请求。请求有效。它包含标记,模型声明它允许包含标记 它适用于我,我已将filters.Add(new ElmahRequestValidationErrorFilter());
添加到 FilterConfig
【参考方案2】:
我现在有一个解决方法,有人在 http://code.google.com/p/elmah/issues/detail?id=217 上提出了建议
您可以强制 ASP 使用旧的请求验证逻辑,方法是将其添加到您的 Web.config
的 <system.web>
部分:
<httpRuntime requestValidationMode="2.0" />
这将影响应用程序的全局,这并不是一个很好的情况。
如果有人有更好的,请告诉我。
【讨论】:
您找到其他解决方案了吗?我们遇到了同样的问题,但恢复到<httpRuntime requestValidationMode="2.0" />
是不行的......【参考方案3】:
看起来这是在当前版本(撰写本文时)1.2.2 之后修复的。我最终在日志中明确记录了错误。这不会作为未处理的异常(电子邮件通知等)进行正常处理,但会将其记录到错误日志中。
catch (Exception ex)
var newEx = new HttpException(500, "Kaboom!!!", ex);
// Adding this explicit ELMAH logging because of ELMAH bug:
// https://code.google.com/p/elmah/issues/detail?id=217
// Waiting for a new ELMAH release with the fix
var elmahError = new Elmah.Error(newEx);
var elmahLog = Elmah.ErrorLog.GetDefault(HttpContext.ApplicationInstance.Context);
elmahLog.Log(elmahError);
throw newEx;
【讨论】:
【参考方案4】:Elmah 中的一个错误已在不久前得到修复,但我认为 google 代码网站上的任何构建都不足以包含它。
正如我在评论中提到的,问题是如果您通过模型(MVC)或控件(Webforms)访问数据,没问题;但是如果你访问 request.Form["inputId"] (Elmah 在构建一个对象时所做的),它每次都会抛出一个异常。
我做了什么:
将google代码项目导出到我的github账号 使用 VS2013 将新的 repo 克隆到我的工作站 选择 NETFX 4.5 构建配置 构建解决方案并运行演示 通过将以下内容添加到 default.aspx,在演示中测试我的案例: 运行一些测试在对测试感到满意后,我通过以下方式将新的 dll 添加到我的解决方案中:
删除当前 Elmah.dll 引用 将 Elmah.dll、Elmah.AspNet.dll 和 AntiXssLibrary.dll 复制/粘贴到我的解决方案中的“lib”文件夹中 添加对三个新 dll 的引用 更新 web.config 使其看起来像演示的 web.config(事情非常相似,但不同 (Elmah = Elmah.AspNet)) 删除 Elmah.mvc 引用(它给我带来了问题) 从 FilterConfig 中删除filters.Add(new HandleErrorAttribute());
(这允许自定义错误继续起作用)
在 Elmah 源码中,关键部分是在 HttpRequestValidation.cs 中使用 request.Unvalidated
【讨论】:
【参考方案5】:您可以通过抛出 HttpException
而不是普通异常来触发 Elmah。
我们的解决方案是使用try/catch
块包装我们的控制器操作代码,并在catch
块中将您的代码引发的异常包装在HttpException
中,然后将其抛出。像这样:
[HttpPost]
[ValidateInput(false)]
public ActionResult MyAction(FormCollection riskyData)
try
//... your code here ...
catch (Exception ex)
throw new HttpException(500, "Internal Server Error", ex);
【讨论】:
【参考方案6】:将以下类添加到您的项目中:
public class ElmahErrorLogModuleFix : ErrorLogModule
protected override void LogException(Exception e, HttpContext context)
if (e == null)
throw new ArgumentNullException("e");
ExceptionFilterEventArgs args = new ExceptionFilterEventArgs(e, (object)context);
this.OnFiltering(args);
if (args.Dismissed)
return;
ErrorLogEntry entry = (ErrorLogEntry)null;
try
//FIX STARTS
//Error error = new Error(e, context);
Error error = CreateErrorSafe(e, context);
//FIX ENDS
ErrorLog errorLog = this.GetErrorLog(context);
error.ApplicationName = errorLog.ApplicationName;
string id = errorLog.Log(error);
entry = new ErrorLogEntry(errorLog, id, error);
catch (Exception ex)
Trace.WriteLine((object)ex);
if (entry == null)
return;
this.OnLogged(new ErrorLoggedEventArgs(entry));
public static Error CreateErrorSafe(Exception e, HttpContext context)
try
var safeFormCollection = new NameValueCollection();
var form = context.Request.Form;
var additionalMessage = string.Empty;
foreach (var key in form.AllKeys)
try
safeFormCollection.Add(key, form[key]);
catch (Exception)
safeFormCollection.Add(key, "_invalid input data_");
additionalMessage += "Form parameter with name=" + key + " has dangerous value. " + Environment.NewLine;
//if no invalid values in form then do as elmah does
if (string.IsNullOrEmpty(additionalMessage))
return new Error(e, context);
var exception = new Exception(additionalMessage, e);
var error = new Error(exception);
error.HostName = TryGetMachineName(context, null);
IPrincipal user = context.User;
if (user != null && NullString(user.Identity.Name).Length > 0)
error.User = user.Identity.Name;
HttpRequest request = context.Request;
//this._serverVariables = Error.CopyCollection(request.ServerVariables);
error.ServerVariables.Add(CopyCollection(request.ServerVariables));
if (error.ServerVariables != null && error.ServerVariables["AUTH_PASSWORD"] != null)
error.ServerVariables["AUTH_PASSWORD"] = "*****";
error.QueryString.Add(CopyCollection(request.QueryString));
error.Form.Add(CopyCollection(safeFormCollection));
error.Cookies.Add(CopyCollection(request.Cookies));
return error;
catch (Exception logEx)
return new Error(new Exception("Error when trying to process error catched by elmah", logEx));
/// <summary>
/// Elmah dll method in Environment.cs
/// </summary>
/// <param name="context"></param>
/// <param name="unknownName"></param>
/// <returns></returns>
public static string TryGetMachineName(HttpContext context, string unknownName)
if (context != null)
try
return context.Server.MachineName;
catch (HttpException ex)
catch (SecurityException ex)
try
return System.Environment.MachineName;
catch (SecurityException ex)
return NullString(unknownName);
/// <summary>
/// Elmah method in Mask.cs
/// </summary>
/// <param name="s"></param>
/// <returns></returns>
public static string NullString(string s)
if (s != null)
return s;
else
return string.Empty;
/// <summary>
/// Elmah method in Error.cs
/// </summary>
/// <param name="collection"></param>
/// <returns></returns>
private static NameValueCollection CopyCollection(NameValueCollection collection)
if (collection == null || collection.Count == 0)
//FIX HERE: cannot allow reutrn null collection as elmah does, because of exception. fix as below
//return (NameValueCollection)null;
return new NameValueCollection();
//FIX ENDS
else
return new NameValueCollection(collection);
/// <summary>
/// Elmah method in Error.cs
/// </summary>
/// <param name="cookies"></param>
/// <returns></returns>
private static NameValueCollection CopyCollection(HttpCookieCollection cookies)
if (cookies == null || cookies.Count == 0)
//FIX HERE: cannot allow reutrn null collection as elmah does, because of exception. fix as below
//return (NameValueCollection)null;
return new NameValueCollection();
//FIX ENDS
NameValueCollection nameValueCollection = new NameValueCollection(cookies.Count);
for (int index = 0; index < cookies.Count; ++index)
HttpCookie httpCookie = cookies[index];
nameValueCollection.Add(httpCookie.Name, httpCookie.Value);
return nameValueCollection;
和
public class ElmahErrorMailModuleFix : ErrorMailModule
private bool _reportAsynchronously2;
protected override void OnInit(HttpApplication application)
base.OnInit(application);
IDictionary config = (IDictionary)this.GetConfig();
if (config == null)
return;
_reportAsynchronously2 = Convert.ToBoolean(GetSetting(config, "async", bool.TrueString));
protected override void OnError(Exception e, HttpContext context)
if (e == null)
throw new ArgumentNullException("e");
ExceptionFilterEventArgs args = new ExceptionFilterEventArgs(e, (object)context);
this.OnFiltering(args);
if (args.Dismissed)
return;
//FIX STARTS
//Error error = new Error(e, context);
Error error = ElmahErrorLogModuleFix.CreateErrorSafe(e, context);
//FIX ENDS
if (this._reportAsynchronously2)
this.ReportErrorAsync(error);
else
this.ReportError(error);
/// <summary>
/// Elmah method in ErrorMailModule.cs
/// </summary>
/// <param name="config"></param>
/// <param name="name"></param>
/// <param name="defaultValue"></param>
/// <returns></returns>
private static string GetSetting(IDictionary config, string name, string defaultValue)
string str = ElmahErrorLogModuleFix.NullString((string)config[(object)name]);
if (str.Length == 0)
if (defaultValue == null)
throw new global::Elmah.ApplicationException(string.Format("The required configuration setting '0' is missing for the error mailing module.", (object)name));
str = defaultValue;
return str;
这些类继承自ErrorLogModule
和ErrorMailModule
,并重写了创建Error
类的方法,因此不会引发HttpRequestValidationException
异常。
然后将这些添加到您的 Web.config:
<add name="ErrorLog" type="YourProject.SomeFolder.ElmahErrorLogModuleFix, YourProject" preCondition="managedHandler" />
<!--and for email module-->
使用这些类而不是原来的类。有点肮脏的黑客,但它有效。
感谢发现的消息 #17 的海报here。
【讨论】:
【参考方案7】:感谢 Amarisi 提供的代码,我已将其重新编写到我的 Global.asax.cs 文件 Application_Error 方法中,以记录 Elmah 未能记录的所有异常。
看起来像这样(已编辑但应该可以工作):
protected void Application_Error(object sender, EventArgs e)
Exception exception = Server.GetLastError();
// ELMAH will not log exceptions arising from POST requests
// when the form values contains 'potentially dangerous' chars
// e.g. html tags
// Adding this explicit ELMAH logging because of ELMAH bug:
// https://code.google.com/p/elmah/issues/detail?id=217
// Waiting for a new ELMAH release with the fix
// get form object
System.Collections.Specialized.NameValueCollection RequestForm = Request.Unvalidated.Form;
// get combined text of form values
string formValues = string.Join(",", RequestForm.AllKeys.Select(key => RequestForm[key]));
// is this the type of POST that Elmah fails on?
if (formValues.Contains("<"))
// log the exception manually
var elmahError = new Elmah.Error(exception);
var elmahLog = Elmah.ErrorLog.GetDefault(Context);
elmahLog.Log(elmahError);
Contains("
很想看看是否有人可以进一步改进。
【讨论】:
以上是关于Elmah 未在 MVC 应用程序中记录 http 发布请求的异常 - 如果请求包含 XML的主要内容,如果未能解决你的问题,请参考以下文章
在 MVC 应用程序中使用 Elmah 进行无懈可击的异常处理
System.Web.HttpContextBase' 不包含带有 Elmah 日志记录的“当前”MVC 4 的定义