How to access StackTrace property form my Custom Exceptions in dot net core - exception

I'm trying to implement my own custom exceptions in dot net core.
This is what I have so far:
public class WSException: Exception
{
// some custom stuff...
private readonly string _developerMessage = "";
public string DeveloperMessage { get { return _developerMessage; } }
public WSException() {}
public WSException(string message) : base(message) {
this._developerMessage = message;
}
public WSException(string message, Exception inner) : base(message, inner) {
this._developerMessage = message;
}
public WSException(Exception ex) : base(ex.Message, ex.InnerException) {
_developerMessage = ex.Message;
Source = ex.Source;
//StackTrace = ex.StackTrace; // cannot be assigned to, it's read only
}
public WSException(string message) : base(message) {
this._developerMessage = (String.IsNullOrWhiteSpace(developerMessage) ? message : developerMessage);
}
}
When I catch a general exception, I try to create one of my own (a WSException) to handle it in a common way, like this:
try {
// whatever
}
catch (WSException e) {
HandleException(e);
}
catch (Exception e) {
HandleException(new WSException(e));
}
When I do it like that, e.Source and e.StackTrace are null, and when I try to assign StackTrace I get a Propery or indexer 'Exception.StackTrace' cannot be assigned to --it is read only.
How should such I implement this constructor?
public WSException(Exception ex) : base(ex.Message, ex.InnerException) {
_developerMessage = ex.Message;
Source = ex.Source;
//StackTrace = ex.StackTrace; // cannot be assigned to, it's read only
}

The workaround I found so far is to handle it when I'm serializing the error to json, something like this:
public class WSExceptionJsonConverter : JsonConverter
{
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
var ex = value as WSException;
writer.WriteStartObject();
// buch of properties[...]
string stackTrace = null;
if (ex.StackTrace != null) {
stackTrace = ex.StackTrace;
} else if (ex.InnerException != null && ex.InnerException.StackTrace != null) {
stackTrace = ex.InnerException.StackTrace;
} else {
stackTrace = null;
}
writer.WritePropertyName("stacktrace");
serializer.Serialize(writer, stackTrace.Split('\n'));
writer.WriteEndObject();
}
But it feels too hacky

Related

NewtonSoft.Json Treating Blank Value as Null but not throwing error

Environment
.net 7
Using Both System.Text.Json
Also NewtonSoft.Json ( 13.0.2)
Example code
string str = #"{
""DateTimeNull"":""""
}";
try
{
var t = System.Text.Json.JsonSerializer.Deserialize<Test>(str);
}
catch (JsonException ex)
{
Console.WriteLine(new { Field = ex.Path , Message = ex.Message });
}
try
{
var t = Newtonsoft.Json.JsonConvert.DeserializeObject<Test>(str);
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
}
public class Test
{
public DateTime? DateTimeNull { get; set; }
}
In above System.Text.Json Deserlizer throw exception but newtonsoft.json line is not throwing any exception. It is converting empty value to null but I want it should thow error and due to limitation I can not move to System.Text.Json as of now.
Payload ( This is i already set in str)
Sample one
#"{
""DateTimeNull"":""""
}";
Expected result: Throw error and should not convert to null.
Sample two.
#"{
""DateTimeNull"": null
}";
Expected result: Should not throw error and it is null value and destination type is null.
I usually recommend to use a JsonConstructor:
var json = #"{
""DateTimeNull"":""""
}";
Test test = JsonConvert.DeserializeObject<Test>(json);
public class Test
{
public DateTime? DateTimeNull { get; set; }
[Newtonsoft.Json.JsonConstructor]
public Test(JToken DateTimeNull)
{
if (DateTimeNull.Type == JTokenType.Null) this.DateTimeNull = null;
else if ((string)DateTimeNull == string.Empty)
throw new JsonException("DateTimeNull property should not be an empty string");
else this.DateTimeNull = DateTimeNull.ToObject<DateTime>();
}
}

My custom middleware exception doesnt work in my .NET Core Web Api application

This is my custom middleware class for exception. I want to handle global exceptions.
public class CustomExceptionMiddleware
{
private readonly RequestDelegate _next;
private readonly ILogger _logger;
public CustomExceptionMiddleware(RequestDelegate next, ILogger<CustomExceptionMiddleware> logger)
{
_logger = logger;
_next = next;
}
public async Task Invoke(HttpContext httpContext)
{
try
{
await _next(httpContext);
}catch (Exception e)
{
await HandleExceptionAsync(httpContext, e);
}
}
private Task HandleExceptionAsync(HttpContext context, Exception exception)
{
var code = HttpStatusCode.InternalServerError;
if(exception is KeyNotFoundException)
{
code = HttpStatusCode.NotFound;
_logger.LogInformation("Exception happend. Exception type: " + exception.GetType().ToString(), new object[0]);
}
else if (exception is UnauthorizedAccessException)
{
code = HttpStatusCode.Unauthorized;
_logger.LogInformation("Exception happend. Exception type: " + exception.GetType().ToString(), new object[0]);
}
else
{
code = HttpStatusCode.BadRequest;
_logger.LogInformation("Exception happend. Bad request ", new object[0]);
}
var result = JsonConvert.SerializeObject(new { error = exception.Message });
context.Response.ContentType = "application/json";
context.Response.StatusCode = (int) code;
//context.ExceptionHandled = true;
return context.Response.WriteAsync(result);
}
}
// Extension method used to add the middleware to the HTTP request pipeline.
public static class CustomExceptionMiddlewareExtensions
{
public static IApplicationBuilder UseCustomExceptionMiddleware(this IApplicationBuilder builder)
{
return builder.UseMiddleware<CustomExceptionMiddleware>();
}
}
I add in my Startup.cs
app.UseCustomExceptionMiddleware();
I don't get logging information that I'm trying to log, and it doesn't throw any exception. What am I missing? Should I throw an exception in a controller class?

Webapi custom JsonMediaTypeFormatter

I am trying to create a custom JSONMediaTypeFormatter in which posts some json parameters to a webapi call. I need to encrypt the returned data from the webapi, hence writing a custom mediatypeformatter.
In my webapiconfig I clear all formatters and only add the custom formatter.
config.Formatters.Clear();
config.Formatters.Add(new CipherMediaFormatter());
In my custom media type formatter I add in the relevant headers and types but i still cant call my web api. It gives me an error
No MediaTypeFormatter is available to read an object of type 'Param' from content with media type 'application/json
The code for the mediatypeformatter
public class CipherMediaFormatter : JsonMediaTypeFormatter
{
private static Type _supportedType = typeof(object);
public CipherMediaFormatter()
{
this.SupportedMediaTypes.Add(new MediaTypeHeaderValue("application/octet-stream"));
this.SupportedMediaTypes.Add(new MediaTypeHeaderValue("text/html"));
this.SupportedMediaTypes.Add(new MediaTypeHeaderValue("application/json"));
}
public override void SetDefaultContentHeaders(Type type, HttpContentHeaders headers, MediaTypeHeaderValue mediaType)
{
base.SetDefaultContentHeaders(type, headers, mediaType);
headers.ContentType = new MediaTypeHeaderValue("application/json");
}
public override bool CanReadType(Type type)
{
return true;
}
public override bool CanWriteType(Type type)
{
return true;
}
public override Task<object> ReadFromStreamAsync(Type type, Stream readStream, HttpContent content, IFormatterLogger formatterLogger)
{
var taskSource = new TaskCompletionSource<object>();
try
{
var ms = new MemoryStream();
readStream.CopyTo(ms);
taskSource.SetResult(ms.ToArray());
}
catch (Exception e)
{
taskSource.SetException(e);
}
return taskSource.Task;
}
public override Task WriteToStreamAsync(Type type, object value, Stream writeStream, HttpContent content, TransportContext transportContext)
{
var taskSource = new TaskCompletionSource<object>();
try
{
if (value != null)
{
var jsonData = JsonConvert.SerializeObject(value);
ICryptographicService cgService = new CryptographicService();
string apiKey = string.Empty;
string pattern = #"api\/(.*)?\/(\d+)?";
var match = Regex.Match(HttpContext.Current.Request.RawUrl, pattern);
....
}
}
catch (Exception e)
{
taskSource.SetException(e);
}
return taskSource.Task;
}
}
The problem was ReadFromStreamAsync causing the mediatypeformatter to error.

Http Post with Blackberry 6.0 issue

I am trying to post some data to our webservice(written in c#) and get the response. The response is in JSON format.
I am using the Blackberry Code Sample which is BlockingSenderDestination Sample. When I request a page it returns with no problem. But when I send my data to our webservice it does not return anything.
The code part that I added is :
ByteMessage myMsg = bsd.createByteMessage();
//myMsg.setStringPayload("I love my BlackBerry device!");
myMsg.setMessageProperty("querytpe","myspecialkey");//here is my post data
myMsg.setMessageProperty("uname","myusername");
myMsg.setMessageProperty("pass","password");
((HttpMessage) myMsg).setMethod(HttpMessage.POST);
// Send message and wait for response myMsg
response = bsd.sendReceive(myMsg);
What am i doing wrong? And what is the alternatives or more efficients way to do Post with Blackberry.
Regards.
Here is my whole code:
class BlockingSenderSample extends MainScreen implements FieldChangeListener {
ButtonField _btnBlock = new ButtonField(Field.FIELD_HCENTER);
private static UiApplication _app = UiApplication.getUiApplication();
private String _result;
public BlockingSenderSample()
{
_btnBlock.setChangeListener(this);
_btnBlock.setLabel("Fetch page");
add(_btnBlock);
}
public void fieldChanged(Field button, int unused)
{
if(button == _btnBlock)
{
Thread t = new Thread(new Runnable()
{
public void run()
{
Message response = null;
String uriStr = "http://192.168.1.250/mobileServiceOrjinal.aspx"; //our webservice address
//String uriStr = "http://www.blackberry.com";
BlockingSenderDestination bsd = null;
try
{
bsd = (BlockingSenderDestination)
DestinationFactory.getSenderDestination
("name", URI.create(uriStr));//name for context is name. is it true?
if(bsd == null)
{
bsd =
DestinationFactory.createBlockingSenderDestination
(new Context("ender"),
URI.create(uriStr)
);
}
//Dialog.inform( "1" );
ByteMessage myMsg = bsd.createByteMessage();
//myMsg.setStringPayload("I love my BlackBerry device!");
myMsg.setMessageProperty("querytpe","myspecialkey");//here is my post data
myMsg.setMessageProperty("uname","myusername");
myMsg.setMessageProperty("pass","password");
((HttpMessage) myMsg).setMethod(HttpMessage.POST);
// Send message and wait for response myMsg
response = bsd.sendReceive(myMsg);
if(response != null)
{
BSDResponse(response);
}
}
catch(Exception e)
{
//Dialog.inform( "ex" );
// process the error
}
finally
{
if(bsd != null)
{
bsd.release();
}
}
}
});
t.start();
}
}
private void BSDResponse(Message msg)
{
if (msg instanceof ByteMessage)
{
ByteMessage reply = (ByteMessage) msg;
_result = (String) reply.getStringPayload();
} else if(msg instanceof StreamMessage)
{
StreamMessage reply = (StreamMessage) msg;
InputStream is = reply.getStreamPayload();
byte[] data = null;
try {
data = net.rim.device.api.io.IOUtilities.streamToBytes(is);
} catch (IOException e) {
// process the error
}
if(data != null)
{
_result = new String(data);
}
}
_app.invokeLater(new Runnable() {
public void run() {
_app.pushScreen(new HTTPOutputScreen(_result));
}
});
}
}
..
class HTTPOutputScreen extends MainScreen
{
RichTextField _rtfOutput = new RichTextField();
public HTTPOutputScreen(String message)
{
_rtfOutput.setText("Retrieving data. Please wait...");
add(_rtfOutput);
showContents(message);
}
// After the data has been retrieved, display it
public void showContents(final String result)
{
UiApplication.getUiApplication().invokeLater(new Runnable()
{
public void run()
{
_rtfOutput.setText(result);
}
});
}
}
HttpMessage does not extend ByteMessage so when you do:
((HttpMessage) myMsg).setMethod(HttpMessage.POST);
it throws a ClassCastException. Here's a rough outline of what I would do instead. Note that this is just example code, I'm ignoring exceptions and such.
//Note: the URL will need to be appended with appropriate connection settings
HttpConnection httpConn = (HttpConnection) Connector.open(url);
httpConn.setRequestMethod(HttpConnection.POST);
OutputStream out = httpConn.openOutputStream();
out.write(<YOUR DATA HERE>);
out.flush();
out.close();
InputStream in = httpConn.openInputStream();
//Read in the input stream if you want to get the response from the server
if(httpConn.getResponseCode() != HttpConnection.OK)
{
//Do error handling here.
}

Logging Exception.Data using Log4Net

We're just getting started with Log4Net (and wishing we'd done it earlier). Whilst we can see inner exceptions, etc. the one thing that seems to be missing from the output when logging an exception is any key/value information held inside the "Exception.Data". Is there anyway we can do this "out of the box"? If not, as we really are only just starting out where should be looking to find a way to implement this functionality?
As an example please see the very basic pseudo code below. We don't want to pollute the exception message with context information just what the problem was (We'd probably have lost more information in the data which would help in investigating the actual problem). But right now all we see in our logs is the type of exception, the message, any stack trace - but no exception "data". This means in our logs we lose the customer id, etc. How can we easily get this information into our logs (without having to code it by hand in each exception catch).
try
{
var ex = new ApplicationException("Unable to update customer");
ex.Data.Add("id", customer.Id);
throw ex;
}
catch(ApplicationException ex)
{
logger.Error("An error occurred whilst doing something", ex);
throw;
}
Following Stefan's lead:
namespace YourNamespace {
public sealed class ExceptionDataPatternConverter : PatternLayoutConverter {
protected override void Convert(TextWriter writer, LoggingEvent loggingEvent) {
var data = loggingEvent.ExceptionObject.Data;
if (data != null) {
foreach(var key in data.Keys) {
writer.Write("Data[{0}]={1}" + Environment.NewLine, key, data[key]);
}
}
}
}
}
And in your configuration add %ex_data and the converter:
<appender ...>
...
<layout type="log4net.Layout.PatternLayout,log4net">
<conversionPattern value="%date %d{HH:mm:ss.fff} [%t] %-5p %c %l - %m%n %ex_data"/>
<converter>
<name value="ex_data" />
<type value="YourNamespace.ExceptionDataPatternConverter" />
</converter>
</layout>
If you have multiple appenders defined you can use a custom renderer rather than defining the converter for every layout.
web/app.config
<log4net>
...
<renderer renderingClass="YourNamespace.ExceptionObjectLogger, YourAssembly" renderedClass="System.Exception" />
...
</log4net>
ExceptionObjectLogger
public class ExceptionObjectLogger : IObjectRenderer
{
public void RenderObject(RendererMap rendererMap, object obj, TextWriter writer)
{
var ex = obj as Exception;
if (ex == null)
{
// Shouldn't happen if only configured for the System.Exception type.
rendererMap.DefaultRenderer.RenderObject(rendererMap, obj, writer);
}
else
{
rendererMap.DefaultRenderer.RenderObject(rendererMap, obj, writer);
const int MAX_DEPTH = 10;
int currentDepth = 0;
while (ex != null && currentDepth <= MAX_DEPTH)
{
this.RenderExceptionData(rendererMap, ex, writer, currentDepth);
ex = ex.InnerException;
currentDepth++;
}
}
}
private void RenderExceptionData(RendererMap rendererMap, Exception ex, TextWriter writer, int depthLevel)
{
var dataCount = ex.Data.Count;
if (dataCount == 0)
{
return;
}
writer.WriteLine();
writer.WriteLine($"Exception data on level {depthLevel} ({dataCount} items):");
var currentElement = 0;
foreach (DictionaryEntry entry in ex.Data)
{
currentElement++;
writer.Write("[");
ExceptionObjectLogger.RenderValue(rendererMap, writer, entry.Key);
writer.Write("]: ");
ExceptionObjectLogger.RenderValue(rendererMap, writer, entry.Value);
if (currentElement < dataCount)
{
writer.WriteLine();
}
}
}
private static void RenderValue(RendererMap rendererMap, TextWriter writer, object value)
{
if (value is string)
{
writer.Write(value);
}
else
{
IObjectRenderer keyRenderer = rendererMap.Get(value.GetType());
keyRenderer.RenderObject(rendererMap, value, writer);
}
}
}
I think a more log4net way of approaching this problem would be to write a PatternLayoutConverter. An example can be found here.
In the convert method you can access your data like this (and write it the way you like):
override protected void Convert(TextWriter writer, LoggingEvent loggingEvent)
{
var data = loggingEvent.ExceptionObject.Data;
}
I think Massimiliano has the right idea but I would modify his solution slightly.
If you plan on sticking all of of your additional data in the dictionary Data within an exception I would change his extension method to the following:
public static class ExLog4Net
{
public static void Error(this ILog log, Exception ex)
{
StringBuilder formattedError = new StringBuilder();
formattedError.AppendFormat("Exception: {0}\r\n", ex.ToString());
foreach (DictionaryEntry de in ex.Data)
formattedError.AppendFormat("{0}: {1}\r\n", de.Key, de.Value);
log.Error(formattedError.ToString());
}
}
You would then stick this method extension in a library you would use in all of your applications. If you don't have one you would have to add this to every project.
you could create an Extension method for your logger to log the customer Id : you should not add important information to the exception
You can abstract the concept of "Additional Information to log" and create an interface with a method that return the additional information you want to log
public interface IDataLogger
{
string GetAdditionalInfo();
}
public class UserDataLogger: IDataLogger
{
public string GetAdditionalInfo()
{
return "UserName";
}
}
public class MoreDataLogger : IDataLogger
{
public string GetAdditionalInfo()
{
return "something";
}
}
you can create different "data Logger" and maybe combine them together
then you could create an generic extension method that get the type of the logger
public static class ExLog4Net
{
public static void Error<T>(this ILog log, Exception ex) where T:IDataLogger,new()
{
var dataLogger=new T();
log.Error(ex.ToString() + " " + dataLogger.GetAdditionalInfo());
}
}
you will be able to do the below:
try
{
}
catch (Exception ex)
{
logger.Error<UserDataLogger>(ex);
logger.Error<MoreDataLogger>(ex);
throw;
}