When an exception is thrown (while debugging in the IDE), i have the opportunity to view details of the exception:
But in code if i call exception.ToString() i do not get to see those useful details:
System.Data.SqlClient.SqlException (0x80131904): Could not find stored procedure 'FetchActiveUsers'.
[...snip stack trace...]
But Visual Studio has some magic where it can copy the exception to the clipboard:
Which gives the useful details:
System.Data.SqlClient.SqlException was unhandled by user code
Message=Could not find stored procedure 'FetchActiveUsers'.
Source=.Net SqlClient Data Provider
ErrorCode=-2146232060
Class=16
LineNumber=1
Number=2812
Procedure=""
Server=vader
State=62
StackTrace:
[...snip stack trace...]
InnerException:
Well i want that!
What would be the contents of:
String ExceptionToString(Exception ex)
{
//todo: Write useful routine
return ex.ToString();
}
that can accomplish the same magic. Is there a .NET function built in somewhere? Does Exception have a secret method somewhere to convert it to a string?
ErrorCode is specific to ExternalException, not Exception and LineNumber and Number are specific to SqlException, not Exception. Therefore, the only way to get these properties from a general extension method on Exception is to use reflection to iterate over all of the public properties.
So you'll have to say something like:
public static string GetExceptionDetails(this Exception exception) {
var properties = exception.GetType()
.GetProperties();
var fields = properties
.Select(property => new {
Name = property.Name,
Value = property.GetValue(exception, null)
})
.Select(x => String.Format(
"{0} = {1}",
x.Name,
x.Value != null ? x.Value.ToString() : String.Empty
));
return String.Join("\n", fields);
}
(Not tested for compliation issues.)
.NET 2.0 compatible answer:
public static string GetExceptionDetails(this Exception exception)
{
PropertyInfo[] properties = exception.GetType()
.GetProperties();
List<string> fields = new List<string>();
foreach(PropertyInfo property in properties) {
object value = property.GetValue(exception, null);
fields.Add(String.Format(
"{0} = {1}",
property.Name,
value != null ? value.ToString() : String.Empty
));
}
return String.Join("\n", fields.ToArray());
}
I first tried Jason's answer (at the top), which worked pretty well, but I also wanted:
To loop iteratively through inner exceptions and indent them.
Ignore null properties and increases readability of the output.
It includes the metadata in the Data property. (if any) but excludes the Data property itself. (its useless).
I now use this:
public static void WriteExceptionDetails(Exception exception, StringBuilder builderToFill, int level)
{
var indent = new string(' ', level);
if (level > 0)
{
builderToFill.AppendLine(indent + "=== INNER EXCEPTION ===");
}
Action<string> append = (prop) =>
{
var propInfo = exception.GetType().GetProperty(prop);
var val = propInfo.GetValue(exception);
if (val != null)
{
builderToFill.AppendFormat("{0}{1}: {2}{3}", indent, prop, val.ToString(), Environment.NewLine);
}
};
append("Message");
append("HResult");
append("HelpLink");
append("Source");
append("StackTrace");
append("TargetSite");
foreach (DictionaryEntry de in exception.Data)
{
builderToFill.AppendFormat("{0} {1} = {2}{3}", indent, de.Key, de.Value, Environment.NewLine);
}
if (exception.InnerException != null)
{
WriteExceptionDetails(exception.InnerException, builderToFill, ++level);
}
}
Call like this:
var builder = new StringBuilder();
WriteExceptionDetails(exception, builder, 0);
return builder.ToString();
This comprehensive answer handles writing out:
The Data collection property found on all exceptions (The accepted answer does not do this).
Any other custom properties added to the exception.
Recursively writes out the InnerException (The accepted answer does not do this).
Writes out the collection of exceptions contained within the AggregateException.
It also writes out the properties of the exceptions in a nicer order. It's using C# 6.0 but should be very easy for you to convert to older versions if necessary.
public static class ExceptionExtensions
{
public static string ToDetailedString(this Exception exception)
{
if (exception == null)
{
throw new ArgumentNullException(nameof(exception));
}
return ToDetailedString(exception, ExceptionOptions.Default);
}
public static string ToDetailedString(this Exception exception, ExceptionOptions options)
{
var stringBuilder = new StringBuilder();
AppendValue(stringBuilder, "Type", exception.GetType().FullName, options);
foreach (PropertyInfo property in exception
.GetType()
.GetProperties()
.OrderByDescending(x => string.Equals(x.Name, nameof(exception.Message), StringComparison.Ordinal))
.ThenByDescending(x => string.Equals(x.Name, nameof(exception.Source), StringComparison.Ordinal))
.ThenBy(x => string.Equals(x.Name, nameof(exception.InnerException), StringComparison.Ordinal))
.ThenBy(x => string.Equals(x.Name, nameof(AggregateException.InnerExceptions), StringComparison.Ordinal)))
{
var value = property.GetValue(exception, null);
if (value == null && options.OmitNullProperties)
{
if (options.OmitNullProperties)
{
continue;
}
else
{
value = string.Empty;
}
}
AppendValue(stringBuilder, property.Name, value, options);
}
return stringBuilder.ToString().TrimEnd('\r', '\n');
}
private static void AppendCollection(
StringBuilder stringBuilder,
string propertyName,
IEnumerable collection,
ExceptionOptions options)
{
stringBuilder.AppendLine($"{options.Indent}{propertyName} =");
var innerOptions = new ExceptionOptions(options, options.CurrentIndentLevel + 1);
var i = 0;
foreach (var item in collection)
{
var innerPropertyName = $"[{i}]";
if (item is Exception)
{
var innerException = (Exception)item;
AppendException(
stringBuilder,
innerPropertyName,
innerException,
innerOptions);
}
else
{
AppendValue(
stringBuilder,
innerPropertyName,
item,
innerOptions);
}
++i;
}
}
private static void AppendException(
StringBuilder stringBuilder,
string propertyName,
Exception exception,
ExceptionOptions options)
{
var innerExceptionString = ToDetailedString(
exception,
new ExceptionOptions(options, options.CurrentIndentLevel + 1));
stringBuilder.AppendLine($"{options.Indent}{propertyName} =");
stringBuilder.AppendLine(innerExceptionString);
}
private static string IndentString(string value, ExceptionOptions options)
{
return value.Replace(Environment.NewLine, Environment.NewLine + options.Indent);
}
private static void AppendValue(
StringBuilder stringBuilder,
string propertyName,
object value,
ExceptionOptions options)
{
if (value is DictionaryEntry)
{
DictionaryEntry dictionaryEntry = (DictionaryEntry)value;
stringBuilder.AppendLine($"{options.Indent}{propertyName} = {dictionaryEntry.Key} : {dictionaryEntry.Value}");
}
else if (value is Exception)
{
var innerException = (Exception)value;
AppendException(
stringBuilder,
propertyName,
innerException,
options);
}
else if (value is IEnumerable && !(value is string))
{
var collection = (IEnumerable)value;
if (collection.GetEnumerator().MoveNext())
{
AppendCollection(
stringBuilder,
propertyName,
collection,
options);
}
}
else
{
stringBuilder.AppendLine($"{options.Indent}{propertyName} = {value}");
}
}
}
public struct ExceptionOptions
{
public static readonly ExceptionOptions Default = new ExceptionOptions()
{
CurrentIndentLevel = 0,
IndentSpaces = 4,
OmitNullProperties = true
};
internal ExceptionOptions(ExceptionOptions options, int currentIndent)
{
this.CurrentIndentLevel = currentIndent;
this.IndentSpaces = options.IndentSpaces;
this.OmitNullProperties = options.OmitNullProperties;
}
internal string Indent { get { return new string(' ', this.IndentSpaces * this.CurrentIndentLevel); } }
internal int CurrentIndentLevel { get; set; }
public int IndentSpaces { get; set; }
public bool OmitNullProperties { get; set; }
}
Top Tip - Logging Exceptions
Most people will be using this code for logging. Consider using Serilog with my Serilog.Exceptions NuGet package which also logs all properties of an exception but does it faster and without reflection in the majority of cases. Serilog is a very advanced logging framework which is all the rage at the time of writing.
Top Tip - Human Readable Stack Traces
You can use the Ben.Demystifier NuGet package to get human readable stack traces for your exceptions or the serilog-enrichers-demystify NuGet package if you are using Serilog. If you are using .NET Core 2.1, then this feature comes built in.
For people who don't want to mess with overriding, this simple non-intrusive method might be enough:
public static string GetExceptionDetails(Exception exception)
{
return "Exception: " + exception.GetType()
+ "\r\nInnerException: " + exception.InnerException
+ "\r\nMessage: " + exception.Message
+ "\r\nStackTrace: " + exception.StackTrace;
}
It does not show the SQLException-specific details you want, though...
There is no secret method. You could probably just override the ToString() method and build the string you want.
Things like ErrorCode and Message are just properties of the exception that you can add to the desired string output.
Update: After re-reading your question and thinking more about this, Jason's answer is more likely what you are wanting. Overriding the ToString() method would only be helpful for exceptions that you created, not already implemented ones. It doesn't make sense to sub class existing exceptions just to add this functionality.
For displaying some details to user you should use ex.Message. For displaying to developers you will probably need ex.Message and ex.StackTrace.
There is no 'secret' method, you could consider Message property to be best fit for user friendly message.
Also be careful that in some case you may have inner exception in exception you catch which would be also useful to log.
You will probably have to manually construct that string by concatenating the various fields you are interested in.
Each left-side name is property in the Exception. If you want to display Message field, you can do
return ex.Message;
Pretty simple. Likewise, the StackTrace can be displayed as below link.
A complete example of StackTrace: http://msdn.microsoft.com/en-us/library/system.exception.stacktrace.aspx
and Exception class: http://msdn.microsoft.com/en-us/library/system.exception.aspx
I think the exception serialization to JSON is nice option. Sample result:
{
"Errors": [{
"Source": ".Net SqlClient Data Provider",
"Number": -1,
"Class": 20,
"Server": "111.168.222.70",
"Message": "A transport-level error has occurred when receiving results from the server. (provider: Session Provider, error: 19 - Physical connection is not usable)"
}
],
"ClientConnectionId": "b1854037-51e4-4943-94b4-72b7bb4c6ab7",
"ClassName": "System.Data.SqlClient.SqlException",
"Message": "A transport-level error has occurred when receiving results from the server. (provider: Session Provider, error: 19 - Physical connection is not usable)",
"Data": {
"HelpLink.ProdName": "Microsoft SQL Server",
"HelpLink.EvtSrc": "MSSQLServer",
"HelpLink.EvtID": "-1",
"HelpLink.BaseHelpUrl": "http://go.microsoft.com/fwlink",
"HelpLink.LinkId": "20476"
},
"InnerException": null,
"HelpURL": null,
"StackTraceString": " at System.Data.SqlClient.SqlConnection.OnError ... DbExecutionStrategy.Execute[TResult](Func`1 operation)",
"RemoteStackTraceString": null,
"RemoteStackIndex": 0,
"ExceptionMethod": "8\nOnError\nSystem.Data, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089\nSystem.Data.SqlClient.SqlConnection\nVoid OnError(System.Data.SqlClient.SqlException, Boolean, System.Action`1[System.Action])",
"HResult": -2146232060,
"Source": ".Net SqlClient Data Provider",
"WatsonBuckets": null
}
If you call ToString on Exception object, you get the class name appended by the message, followed by inner exception and then the stack trace.
className + message + InnerException + stackTrace
Given that, InnerException and StackTrace are only added if they are not null. Also, the fields you have mentioned in the screenshot are not part of standard Exception class. Yes, exception does offer a public property called "Data", that contain additional user-defined information about the exception.
In visual studio that sort of information can be outputted by a debugger visualizer.
I assume that because it is possible to write your own debugger visualizer:
http://msdn.microsoft.com/en-us/library/e2zc529c.aspx
That in theory, if your can reverse engineer the built-in debugger visualizer for exceptions (if your can work out where they are stored) then you could use the same functionality.
EDIT:
Here is a post about where the debugger visualizers are kept: Where do I find Microsoft.VisualStudio.DebuggerVisualizers?
You might be able to use it for your own purposes.
Related
I have a helper method in C# that tries to get object properties (like turning them into Dictionary object with key-value pair). It is similar to:
public static Dictionary<string, object?> GetPropertyValues(object obj, int currentLevel = 0, int maxLevel = 3)
{
Dictionary<string, object?> propertyValues = new Dictionary<string, object?>();
if (currentLevel >= maxLevel)
{
propertyValues.Add("currentLevel", currentLevel);
propertyValues.Add("maxLevel", maxLevel);
return propertyValues;
}
foreach (PropertyDescriptor descriptor in TypeDescriptor.GetProperties(obj))
{
string name = descriptor.Name;
object? value = descriptor.GetValue(obj);
if (value != null)
{
if (!value.GetType().IsPrimitive)
{
value = GetPropertyValues(value, ++currentLevel, maxLevel);
}
}
propertyValues.Add(name, value);
}
return propertyValues;
}
but if I send and instance of a Process class then following line throws exception:
object? value = descriptor.GetValue(obj);
Property accessor 'SafeHandle' on object 'System.Diagnostics.Process'
threw the following exception:'Access is denied.'
I can also see Visual Studio debugger shows the same error;
Is there any way to avoid this error or skip properties if they will throw exception?
While I can cover the line with try/catch I am trying to find a native way to run execution without exception driven way.
When Debug.Assert fails, it shows a very unhelpful error:
Assertion failed
This can be improved to get at least some information about the location of the error, like function name, file, and line number like this:
public static void MyAssert(bool expr,
[System.Runtime.CompilerServices.CallerMemberName] string memberName = "",
[System.Runtime.CompilerServices.CallerFilePath] string sourceFilePath = "",
[System.Runtime.CompilerServices.CallerLineNumber] int sourceLineNumber = 0)
{
string message = $"Assertion failed in {memberName} ({sourceFilePath}:{sourceLineNumber})";
Debug.Assert(expr, message);
}
This helps, but what would be really cool was to be able to see the expression the caller wanted to assert, something like this:
public static void MyAssert(bool expr,
[PreviousCallerArgumentAsString] string argument) /* wish this worked */
{
string message = $"Assertion '{argument}' failed";
Debug.Assert(expr, message);
}
I am just transitioning to C# but in C++ this could be done using a macro like this:
#define ASSERT(Expr) Debug.Assert(Expr, "Assertion " #Expr " failed.")
ASSERT(1 + 1 == 3);
/* would expand to */
Debug.Assert(1 + 1 == 3, "Assertion 1 + 1 == 3 failed.");
In my code, I use asserts pretty liberally and having to retype the expression slows you down a ton.
Is there a way something like this could be achieved in C#?
You're in luck! This functionality was implemented in C# 10, using [CallerArgumentExpression].
For example:
public static class Debug
{
public static void Assert(bool condition, [CallerArgumentExpression("condition")] string message = null)
{
if (!condition)
{
Console.WriteLine($"Assert failed! {message}");
}
}
}
When used with:
Debug.Assert(true == false);
Prints:
Assert failed! true == false
See it on SharpLab.
Here is an example of what I want to do:
MessageBox.Show("Error line number " + CurrentLineNumber);
In the code above the CurrentLineNumber, should be the line number in the source code of this piece of code.
How can I do that?
In .NET 4.5 / C# 5, you can get the compiler to do this work for you, by writing a utility method that uses the new caller attributes:
using System.Runtime.CompilerServices;
static void SomeMethodSomewhere()
{
ShowMessage("Boo");
}
...
static void ShowMessage(string message,
[CallerLineNumber] int lineNumber = 0,
[CallerMemberName] string caller = null)
{
MessageBox.Show(message + " at line " + lineNumber + " (" + caller + ")");
}
This will display, for example:
Boo at line 39 (SomeMethodSomewhere)
There's also [CallerFilePath] which tells you the path of the original code file.
Use the StackFrame.GetFileLineNumber method, for example:
private static void ReportError(string message)
{
StackFrame callStack = new StackFrame(1, true);
MessageBox.Show("Error: " + message + ", File: " + callStack.GetFileName()
+ ", Line: " + callStack.GetFileLineNumber());
}
See Scott Hanselman's Blog entry for more information.
[Edit: Added the following]
For those using .Net 4.5 or later, consider the CallerFilePath, CallerMethodName and CallerLineNumber attributes in the System.Runtime.CompilerServices namespace. For example:
public void TraceMessage(string message,
[CallerMemberName] string callingMethod = "",
[CallerFilePath] string callingFilePath = "",
[CallerLineNumber] int callingFileLineNumber = 0)
{
// Write out message
}
The arguments must be string for CallerMemberName and CallerFilePath and an int for CallerLineNumber and must have a default value. Specifying these attributes on method parameters instructs the compiler to insert the appropriate value in the calling code at compile time, meaning it works through obfuscation. See Caller Information for more information.
I prefer one liners so:
int lineNumber = (new System.Diagnostics.StackFrame(0, true)).GetFileLineNumber();
In .NET 4.5 you can get the line number by creating the function:
static int LineNumber([System.Runtime.CompilerServices.CallerLineNumber] int lineNumber = 0)
{
return lineNumber;
}
Then each time you call LineNumber() you will have the current line. This has the advantage over any solution using the StackTrace that it should work in both debug and release.
So taking the original request of what is required, it would become:
MessageBox.Show("Error enter code here line number " + LineNumber());
This is building on the excellent answer by Marc Gravell.
For those who need a .NET 4.0+ method solution:
using System;
using System.IO;
using System.Diagnostics;
public static void Log(string message) {
StackFrame stackFrame = new System.Diagnostics.StackTrace(1).GetFrame(1);
string fileName = stackFrame.GetFileName();
string methodName = stackFrame.GetMethod().ToString();
int lineNumber = stackFrame.GetFileLineNumber();
Console.WriteLine("{0}({1}:{2})\n{3}", methodName, Path.GetFileName(fileName), lineNumber, message);
}
How to call:
void Test() {
Log("Look here!");
}
Output:
Void Test()(FILENAME.cs:104)
Look here!
Change the Console.WriteLine format how you like!
If its in a try catch block use this.
try
{
//Do something
}
catch (Exception ex)
{
System.Diagnostics.StackTrace trace = new System.Diagnostics.StackTrace(ex, true);
Console.WriteLine("Line: " + trace.GetFrame(0).GetFileLineNumber());
}
You only asked about the line number and with nullable project type, you then need to use a something like this
internal class Utils
{
public static int Line([CallerLineNumber] int? lineNumber =null)=>lineNumber;
}
in your code, if you like to get a line number you then just call
var line=Utils.Line();
if you are logging and you would like to document the line number in say logging than call the method like this
public void MyMethod(int someValue)
{
switch(someValue)
{
case 1:
if(abc<xyz)
{
logger.LogInformation("case value {someValue} this line {line} was true", someValue ,Utils.Line()-2);
}
break;
case 2:
logger.LogInformation("case value {someValue} this {line} was executed",someValue,Utils.Line());
break;
caste 3:
logger.LogInformation("case value {someValue} this {line} was executed",someValue,Utils.Line());
break;
}
}
You can extend this pattern with any of the other [CallerXXX] methods and not use them where ever, not just in the method parameters.
in the Nuget Package Walter I use a super cool class named ExceptionObject
if you import the NuGet package you have some nice extension methods on the Exception class as well as access to a CallStack showing the call chain including method parameters and parameter values of all methods called.
It's like a stack of an exception only with values showing how you got where you got with what values.
public void MyMethod()
{
try
{
//get me all methods, signatures, parameters line numbers file names etc used as well as assembly info of all assemblies used for documentation of how the code got here
var stack= new CallStack();
foreach( var frame in StackedFrames)
{
logger.LogDebug(frame.ToString());
}
}
catch(SqlException e)
{
var ex = new ExceptionObject(e);
logger.LogException(e,"{this} exception due to {message} {server} {procedure} TSQL-line:{sqlline}\n{TSQL}"
,e.GetType().Name
,e.Message
,ex.SqlServer
,ex.SqlProcedureName
,ex.SqlLineNumber
,ex.Tsql
,ex.CallStack);
}
catch(Exception e)
{
var ex = new ExceptionObject(e);
logger.LogException(e,"{this} exception due to {message} signature: signature}\nCallStack:", e.GetType().Name,e.Message,ex.Signature,ex.CallStack);
}
}
I'm trying to customize the dialogue between uCommerce and DIBS to allow ticket registrations.
So far, I've succeeded in copying the DibsPageBuilder and adding the maketicket parameter. However, I need to customize the callback made to the function ProcessCallcack in the DibsPaymentMethodService class.
I copied the ProcessCallback and its content carefully and added it to my custom class, but when I run the checkout pipeline I keep getting the following error:
UCommerce.Pipelines.PipelineException: Exception occoured while processing pipeline
'UCommerce.Pipelines.Checkout.CheckoutPipeline'. See inner exception for details.
---> System.Security.SecurityException: Payment insufficient to cover order total
for OrderGuid xxx. Please ensure that payments cover the entire value of the order
before checking out. at
Commerce.Pipelines.Checkout.ValidatePaymentsMadeAgainstOrderTotalTask.Execute(PurchaseOrder subject)
at UCommerce.Pipelines.Pipeline`1.Execute(T subject) --- End of inner exception
stack trace ---...
I don't get why the checkout pipeline fail to succeed when I manually override the ProcessCallback with the same code the function usually is made of. In other words, if I don't override ProcessCallback the function runs smoothly, but then I don't get to do my customization. I have to say, that the error occurs before I customize anything.
My current uCommerce platform runs version 2.6.1. The code I've copied is:
public override void ProcessCallback(Payment payment)
{
if (payment.PaymentStatusId != 10000001)
return;
DibsPaymentMethodServiceConfigurationSection instance =
DibsPaymentMethodServiceConfigurationSection.Instance;
string s = HttpContext.Current.Request["transact"];
if (string.IsNullOrEmpty(s))
throw new ArgumentException("transact must be present in query string.");
int result;
if (!int.TryParse(s, out result))
throw new FormatException("transact must be a valid int32");
PaymentStatusCode paymentStatusCode = PaymentStatusCode.Authorized;
if (instance.UseMd5)
{
string parameter1 = this.GetParameter("authkey", "When using md5 \"{0}\" cannot be null or empty", new string[1]
{
"authkey"
});
string parameter2 = this.GetParameter("currency", "When using md5 \"{0}\" cannot be null or empty", new string[1]
{
"currency"
});
string parameter3 = this.GetParameter("amount", "When using md5 \"{0}\" cannot be null or empty", new string[1]
{
"amount"
});
int currencyNumber = new CurrencyCodeTranslater().FromIsoCode(parameter2);
string postMd5Key = new DibsMd5Computer().GetPostMd5Key(result.ToString(), parameter3, currencyNumber);
if (!parameter1.Equals(postMd5Key))
paymentStatusCode = PaymentStatusCode.Declined;
}
payment.PaymentStatus = PaymentStatus.Get((object) paymentStatusCode);
payment.TransactionId = result.ToString();
this.ProcessPaymentRequest(new PaymentRequest(payment.PurchaseOrder, payment));
}
Many thanks in advance.
/brinck10
HtmlHelper.GetTagsAndValues(htmlContent);
and i get this error:
at System.String.Split(String[] separator, Int32 count, StringSplitOptions options)
at System.String.Split(String[] separator, StringSplitOptions options)
at WebCrawler.Logic.CrawlerManager.UseRulesOnHtmlPage(Agencies agency, String pageUrl, List`1 listTagValuePair, RulesGroups ruleGroup) in D:\PROJEKTI\crawler\WebCrawlerSuite\WebCrawler.Logic\CrawlerManager.cs:line 263
at WebCrawler.Logic.CrawlerManager.GetAdvertismentFromHtmlContent(List`1 listTagValuePair, Agencies agency, String pageUrl) in D:\PROJEKTI\crawler\WebCrawlerSuite\WebCrawler.Logic\CrawlerManager.cs:line 191
at WebCrawler.Logic.CrawlerManager.ImportAdvertisment2Database.Work(Crawler crawler, PropertyBag propertyBag) in D:\PROJEKTI\crawler\WebCrawlerSuite\WebCrawler.Logic\CrawlerManager.cs:line 668
at WebCrawler.Logic.CrawlerManager.ImportAdvertisment2Database.Process(Crawler crawler, PropertyBag propertyBag) in D:\PROJEKTI\crawler\WebCrawlerSuite\WebCrawler.Logic\CrawlerManager.cs:line 584
i read this article:
http://blogs.msdn.com/b/ericlippert/archive/2009/06/08/out-of-memory-does-not-refer-to-physical-memory.aspx
How can i prevent this error?
whole method:
public static List<TagValuePair> GetTagsAndValues(string htmlContent)
{
List<TagValuePair> tagsValues = new List<TagValuePair>();
Dictionary<string, int> tagAppearance = new Dictionary<string, int>();
HtmlDocument doc = new HtmlDocument();
if (htmlContent != null)
{
doc.LoadHtml(htmlContent);
if (doc.DocumentNode.SelectNodes("//*") == null)
{
List<TagValuePair> tempList = new List<TagValuePair>();
tempList.Add(new TagValuePair("Error!", htmlContent, -1));
return tempList;
}
foreach (HtmlNode tag in doc.DocumentNode.SelectNodes("//*"))
{
try
{
if (!string.IsNullOrEmpty(tag.InnerHtml.Trim()))
{
if (!tagAppearance.Keys.Contains(tag.Name))
{
tagAppearance.Add(tag.Name, 1);
}
else
tagAppearance[tag.Name] = tagAppearance[tag.Name] + 1;
tagsValues.Add(new TagValuePair(tag.Name, tag.InnerHtml.Trim(), tagAppearance[tag.Name]));
}
else
{
// Help link: http://refactoringaspnet.blogspot.com/2010/04/using-htmlagilitypack-to-get-and-post_19.html
if (!string.IsNullOrEmpty(tag.GetAttributeValue("value", "").Trim()))
{
if (!tagAppearance.Keys.Contains("option value"))
{
tagAppearance.Add("option value", 1);
}
else
tagAppearance["option value"] = tagAppearance["option value"] + 1;
tagsValues.Add(new TagValuePair("option value", tag.GetAttributeValue("value", "").Trim(), tagAppearance["option value"]));
}
if (tag.NextSibling != null && !string.IsNullOrEmpty(tag.NextSibling.InnerHtml.Trim()))
{
if (!tagAppearance.Keys.Contains(tag.Name))
{
tagAppearance.Add(tag.Name, 1);
}
else
tagAppearance[tag.Name] = tagAppearance[tag.Name] + 1;
tagsValues.Add(new TagValuePair(tag.Name, tag.NextSibling.InnerHtml.Trim(), tagAppearance[tag.Name]));
}
}
}
catch (Exception)
{
return null;
}
}
}
EDIT:
exact error is here:
doc.LoadHtml(htmlContent);
I would suggest looking at a memory profiler to ensure you haven't got any leaks in your application. Given you say it occurs after 12 hours of app working, it seems to indicate that it may be a slow leak that eventually causes the OutOfMemory exception.
There are a number of ways that you can unitentionally hold onto references that will cause a slow leak. Running a profiler will help you identify these issues. It may not be the one line of code that is causing the problem. It may just be that the one line of code is often showing you the straw that breaks the camels back.
I have used Redgates Ants Profiler before (it comes with a 14 day free trial), and it helped me heaps to get memory usage down and increase performance. I seem to be plugging this a lot recently, but it is purely due to the fact I find it to be a very valuable tool.
Take a look through some of their walkthroughs and/or vidoes to see how to track down a leak.