I want to get back good error info from a WebApi action, so if there's an exception, I do this:
catch (Exception e)
{
return Content(HttpStatusCode.InternalServerError, e);
}
In another WebApi which consumes the first one, I do this so that I can view that exception, in case of an error:
if (response.IsSuccessStatusCode)
{
return await response.Content.ReadAsJsonAsync<MyModel>();
}
Exception x = await response.Content.ReadAsJsonAsync<Exception>();
// ...look at exception information
The problem is that it's not being serialized/deserialized correctly. Specifically, I'm not getting the array of Error objects in the deserialized Exception object, nor am I getting the StackTrace. It looks like the object is getting serialized incorrectly, so it won't deserialize into the Exception object. For example, the StackTrace property is coming across the wire as "StackTraceString." Obviously, that's not going to deserialize into a property called "StackTrace."
Anyone know why some of the property names are changed when serializing?
Related
I would like to be able to access properties on the original exception that is thrown from a Consumer inside a Fault Consumer. For example, if the unhandled exception is a ValidationException with a collection of Errors, am I able to access that collection from a Fault Consumer?
The only thing I seem to have access to is the ExceptionType and the Message. I suppose I could parse the exception message to get the Errors collection, but is there a way to achieve this without parsing the message and generating the collection?
public async Task Consume(ConsumeContext<Fault<MyMessage>> context)
{
string exceptionType = context.Message.Exceptions[0].ExceptionType;
string exceptionMessage = context.Message.Exceptions[0].Message;
if (exceptionType == "FluentValidation.ValidationException")
{
// here I want to get the Errors collection on the exception of type ValidationException
}
}
MassTransit does not serialize Exception, it encapsulates the exception details in an ExceptionInfo type that is included with the Fault event.
There is no access to the original Exception type, and for good reason. Serializing exceptions as part of a message contract is just bad practice, in my opinion.
I have been debugging an issue with my newly minted WCF services Fault contract and finally found out what was breaking it.
I defined the service like so:
[ServiceContract]
public interface IService1
{
[OperationContract]
[FaultContract(typeof(ApplicationException))]
string GetData();
}
in my service I was handling exception in the service like so:
public string GetData()
{
try
{
// do stuff
}
catch(Exception e)
{
ApplicationException ae = new ApplicationException("oh dear!", e );
throw new FaultException<ApplicationException>( ae,
new FaultReason(ae.Message));
}
}
However, the client would never receive the fault exception, instead it would get an exception which said:
An error occurred while receiving the HTTP response to ... This could
be due to the service endpoint binding not using the HTTP protocol.
This could also be due to an HTTP request context being aborted by the
server ( possibly due to the service shutting down).See server logs
for more details
If I changed my code on the service like so (ie: do NOT set the inner exception when constructing the ApplicationException) it works as expected.
public string GetData()
{
try
{
// do stuff
}
catch(Exception e)
{
ApplicationException ae = new ApplicationException("oh dear!");
throw new FaultException<ApplicationException>( ae,
new FaultReason(ae.Message));
}
}
Can anyone explain why this might fail if the inner exception is set? I could not see it anywhere in the documentation.
When the ApplicationException is sent to the client via FaultException<T> without the InnerException, then it is sent only as a string. However, when the InnerException is set, then the ApplicationException itself is sent.
The Exception type is serializable in .NET (it is often incorrectly cited as not being serializable), however, frequently the contents of the Data property are not serializable. This will cause a serialization issue which is what I believe you are experiencing.
There are a few workarounds to this issue: You can set the Data property to null by using reflection or you can create your own class.
I use the following code to call a web method and get some info. To tidy things up a bit and get some code reuse, I introduced the InvokeWebMethod routine.
private T InvokeWebMethod<T>(webServiceDelegate d)
{
return (T)base.invokeWebMethod(d);
}
internal XmlElement GetInfo(string url)
{
return this.InvokeWebMethod<XmlElement>(() => { return this.myService.GetInfo(url); });
}
If the web method does not exist on the server, a 404 Exception is raised inside the delegate.
The bit that I don't understand, is that the Lambda function ignores the 404 exception and instead raises an XmlElement Cast exception.
Can any one explain to me why the 404 Exception isn't raised up the stack until it is handled?
Thanks
You gotta check the InnerException property of the exception you are getting, chances are the 404 exception is somewhere down the stack.
I'm writing a WCF Service, and was wondering if there is a recommended pattern for returning various messages from a service method.
For example, if I call the public User Login(string userName, string passWord) method in my service, I want it to return my user object when they are valid users. However, if the details are not correct, or the account is disabled, I want to be able to return that data to the client.
I initially thought I would throw a LogonFailureException (custom exception class) when one of these two scenarios occurred. In the client, I could then catch this type and process it as I wanted. However, if I'm debugging the application, it breaks when the exception is thrown (even if I wrap it in a FaultException).
I could also create a ServiceResponse class that all my methods use as a return type - in there I could then have a "Messages" collection, but that seems potentially counter-intuitive. I'd rather the returntypes of my methods were simply what I expect to get back in normal circumstances.
So, bottom line, is there a particular pattern of dealing with messages coming back from the service method that is recommended?
Thanks
You need to throw a strongly-typed soap fault:
[DataContract]
public class ConnectionFault
{
[DataMember]
public string Issue { get; set; }
[DataMember]
public string Details { get; set; }
}
[FaultContract(typeof(ConnectionFault))]
[FaultContract(typeof(DataReaderFault))]
[OperationContract]
Int16 GetInStock(int productId);
//when it's time to throw the exception
var connectionFault = new ConnectionFault();
connectionFault.Issue = "Problem connecting to the database";
connectionFault.Details = ex.Message;
throw new FaultException<ConnectionFault>(connectionFault);
Then the client can catch it.
In my opinion, There are 2 scenarios.
1. You wish to throw a sort of exception on the Service side.
2. You wish to return a class that contains error data of something went wrong in Service.
What is the difference ? The first would be just like yaron wrote, you throw and catch a FaultException.
The second would be just to return an object, for example if something went wrong in your service, you always return ErrorData - this is a data contract that contains message and exception stack as string - you can add ErrorData.Update method, and every time you catch a new exception in service, update the ErrorData message.. this is good for long running calls that might catch several exception but you dont want to abort on the first one.
No matter which of these you choose, DO NOT INHERIT from Exception, I have read this is bad practice, you dont want to serialize and transfer Exception based data contracts over WCF.
I've a few web methods that I use to call some external services like the Google Calendar API, obviously these can be extremely brittle.
Unfortunately I now realise that any error thrown on these methods are not causing an exception to bubble up to Global.asax which is where errors are getting logged in this application.
I have seen suggestions to wrap the method in a try/catch, which is a stupid way of doing it as there are a variety of errors that ASP.Net will silently swallow still.
In trying to find a solution I've seen a lot of references to SoapExtension, which is exactly what I want to do but doesn't get fired as I'm returning Json. What I really want is a way to catch the error just like that.
Any pointers appreciated, I still can't understand how the ASP.Net team could have thought that silently swallowing errors like this was a bright idea.
So for example a method like this:
[WebMethod]
[ExceptionHandling] //can I write a handler like this to catch exceptions from JSON webservices?
static public void DeleteItem(string id)
{
var api = new GoogleCalendarAPI(User.InternalUser());
api.DeleteEvent(id);
return "success";
}
There is no equivalent to SoapExtension for JSON WebMethods and having custom errors turned on in your production site will result in a generic error message being returned to the client, no error is ever raised on the server. You cannot circumvent this.
If you inspect the code using something like ILSpy, there is no way to pass a method or class to page WebMethods like SoapExtension. The error is swallowed by ASP.Net as it invokes the web method, the only notification you will get is a HTTP 500 error sent to the client with a total generic error message.
In 4.0, WebMethods get called by this:
// System.Web.Script.Services.RestHandler
internal static void ExecuteWebServiceCall(HttpContext context, WebServiceMethodData methodData)
{
try
{
//snip irrelevant code
RestHandler.InvokeMethod(context, methodData, rawParams);
}
catch (Exception ex)
{
RestHandler.WriteExceptionJsonString(context, ex);
}
}
So if invoking your method throws an error it will call the following code with a statusCode of 500, there's no re-throw in there and nothing else you can pass in called so unless I'm being blind it just gets swallowed silently. Even worse if you've got custom errors turned on, which any sane person will, it'll completely obfuscate the original cause:
// System.Web.Script.Services.RestHandler
internal static void WriteExceptionJsonString(HttpContext context, Exception ex, int statusCode)
{
//snip code setting up response
context.Response.TrySkipIisCustomErrors = true;
using (StreamWriter streamWriter = new StreamWriter(context.Response.OutputStream, new UTF8Encoding(false)))
{
if (ex is TargetInvocationException)
{
ex = ex.InnerException;
}
if (context.IsCustomErrorEnabled)
{
streamWriter.Write(JavaScriptSerializer.SerializeInternal(RestHandler.BuildWebServiceError(AtlasWeb.WebService_Error, string.Empty, string.Empty)));
}
else
{
streamWriter.Write(JavaScriptSerializer.SerializeInternal(RestHandler.BuildWebServiceError(ex.Message, ex.StackTrace, ex.GetType().FullName)));
}
streamWriter.Flush();
}
}
I can't see a way around it, looks like WebMethod is not ready for production code, shame.
It's not so much they get disappeared, it's more that they get passed out to the calling client. Since however you don't always want to (or should) reveal such intimate details of your service, you can prevent errors bubbling out of your service. This gives the impression of them disappearing.
Wrapping the inner detail in a try-catch is about the best way to cope with any errors. Within the method you're dealing with standard error trapping. So I think you'd want something like:
[WebMethod]
static public string DeleteItem(string id)
{
try
{
var api = new GoogleCalendarAPI(User.InternalUser());
api.DeleteEvent(id);
return "success";
}
catch(Exception ex)
{
log.fatal(ex);
return "error";
}
}
If anything throws an exception within the try-catch it'll be caught. ASP.Net won't interfere with it, unless the methods you are calling have been specifically coded to do so.
Edit
If the GoogleCalendarAPI class is in turn calling a method, such as ExecuteWebServiceCall with catches the Exception, then you'd have to parse the response. I'd hope they gave you some other clue, like a response code, to indicate an error state. You could then wrap that in an Exception, throw it have it caught by your default error handler.