1

I'm migrating an application from full framework to net core. I'm struggling to get the exception manager for the apis to get to work.

I'm currently using a attribute based solution.

I have this class

public class ApiExceptionFilterAttribute : ExceptionFilterAttribute
{
    public override async void OnException(ExceptionContext context)
    {
        context.HttpContext.Response.StatusCode = (int)HttpStatusCode.InternalServerError;
        var ex = context.Exception;
        if (ex == null) return;

        context.HttpContext.Response.ContentType = "application/json";

        using (var writer = new StreamWriter(context.HttpContext.Response.Body))
        {
            var js = new JsonSerializer();
            js.ReferenceLoopHandling = ReferenceLoopHandling.Ignore;
            js.Serialize(writer, ex);
            await writer.FlushAsync().ConfigureAwait(false);
        }
    }
}

and I use the corresponding attribute on the desired controller. [ApiExceptionFilter]

The thing is, that when I catch the exception and throw it to the client as a JSON string. I got a 27mb download, and its because it's serializing the TargetSite and within of that it serialize the DefinedTypes which is a list of assemblies and types.

Does somebody know how can I serialize the exception avoiding those properties?

Here is an example of the exception:

{
Id: null,
Message: "Hubo errores en la validación de los parámetros de la operación.",
Data: { },
InnerException: {
Detail: {
Details: [
{
Key: "Cuit",
Message: "The field Cuit must match the regular expression '^[0-9]{11}$'.",
ParameterName: "request"
}
]
},
Action: null,
Code: {
IsPredefinedFault: true,
IsSenderFault: true,
IsReceiverFault: false,
Namespace: "http://schemas.xmlsoap.org/soap/envelope/",
Name: "Client",
SubCode: null
},
Message: "Hubo errores en la validación de los parámetros de la operación.",
Reason: {
Translations: [
{
XmlLang: "en-US",
Text: "Hubo errores en la validación de los parámetros de la operación."
}
]
},
Data: { },
InnerException: null,
TargetSite: {
Name: "HandleReply",
DeclaringType: "System.ServiceModel.Channels.ServiceChannel, System.Private.ServiceModel, Version=4.5.0.3, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a",
ReflectedType: "System.ServiceModel.Channels.ServiceChannel, System.Private.ServiceModel, Version=4.5.0.3, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a",
MemberType: 8,
MetadataToken: 100671507,
Module: {
MDStreamVersion: 131072,
FullyQualifiedName: "C:\Users\rotha\.nuget\packages\system.private.servicemodel\4.5.3\runtimes\win\lib\netstandard2.0\System.Private.ServiceModel.dll",
ModuleVersionId: "c5851175-5eba-412a-8f03-6cd647a2a897",
MetadataToken: 1,
ScopeName: "System.Private.ServiceModel.dll",
Name: "System.Private.ServiceModel.dll",
Assembly: {
CodeBase: "file:///C:/Users/xxx/.nuget/packages/system.private.servicemodel/4.5.3/runtimes/win/lib/netstandard2.0/System.Private.ServiceModel.dll",
FullName: "System.Private.ServiceModel, Version=4.5.0.3, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a",
EntryPoint: null,
DefinedTypes: [
"FxResources.System.Private.ServiceModel.SR, System.Private.ServiceModel, Version=4.5.0.3, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a",
"System.NotImplemented, System.Private.ServiceModel, Version=4.5.0.3, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a",
"System.__HResults, System.Private.ServiceModel, Version=4.5.0.3, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a",
"System.FxTrace, System.Private.ServiceModel, Version=4.5.0.3, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a",
"System.SR, System.Private.ServiceModel, Version=4.5.0.3, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a",
"System.Xml.XmlBinaryNodeType, System.Private.ServiceModel, Version=4.5.0.3, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a",
"System.Collections.Generic.KeyedByTypeCollection`1, System.Private.ServiceModel, Version=4.5.0.3, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a",
"System.Collections.Generic.SynchronizedCollection`1, System.Private.ServiceModel, Version=4.5.0.3, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a",
"System.Collections.Generic.SynchronizedKeyedCollection`2, System.Private.ServiceModel, Version=4.5.0.3, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a",
"System.Collections.Generic.SynchronizedReadOnlyCollection`1, System.Private.ServiceModel, Version=4.5.0.3, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a",
..........

this list of defined types, has like a million of items... :(

dbc
  • 104,963
  • 20
  • 228
  • 340
Rothariger
  • 293
  • 3
  • 22
  • Its because you are writing the response body to the stream. Instead just pass the info about the exception. – Jaya Oct 03 '18 at 18:25
  • sorry i'm not following you... – Rothariger Oct 03 '18 at 18:28
  • i need to send the exception as a json string, i guess i didn't explain that. – Rothariger Oct 03 '18 at 18:29
  • Why are you writing the exception to the response? Seems like that would leak details to the consuming application, which doesn't need to know *how* the API failed. Just that it failed. Seems like you should log the error and then write a generic "Something went wrong" response for unhandled exceptions. – mason Oct 03 '18 at 18:33
  • yes, you have a point there. – Rothariger Oct 03 '18 at 18:38
  • but, this i how is working now in full framework, but it doesnt serialize those properties DefinedTypes and TargetSite. – Rothariger Oct 03 '18 at 18:39
  • What are the values of [`((DefaultContractResolver)js.ContractResolver).IgnoreSerializableInterface`](https://www.newtonsoft.com/json/help/html/P_Newtonsoft_Json_Serialization_DefaultContractResolver_IgnoreSerializableInterface.htm) and [`((DefaultContractResolver)js.ContractResolver).IgnoreSerializableAttribute`](https://www.newtonsoft.com/json/help/html/P_Newtonsoft_Json_Serialization_DefaultContractResolver_IgnoreSerializableAttribute.htm)? – dbc Oct 03 '18 at 18:56
  • Also, what is the type of the exception you are serializing -- i.e. the return of `GetType()`? – dbc Oct 03 '18 at 19:11
  • The difference is likely related to [this change](https://stackoverflow.com/a/50881884), but I can't quite pin down where it's happening in the source, since `Exception` is marked as both `[Serializable]` and `ISerializable` in the [.Net core source code](https://github.com/dotnet/coreclr/blob/master/src/System.Private.CoreLib/src/System/Exception.cs) (maybe that's the wrong location for the source?), and I don't have .Net core to debug. Maybe the custom contract resolver from https://stackoverflow.com/a/50881884 will resolve the problem? – dbc Oct 03 '18 at 19:23
  • i'm currently trying a System.ServiceModel.EndpointNotFoundException but it could be any exception. – Rothariger Oct 03 '18 at 19:24
  • Create a new object with the properties you want. `new { ex.Message, ex.StackTrace, ... }`. – Heretic Monkey Oct 03 '18 at 23:11
  • well i have some faultexceptions from WCF services, on those cases that wouldn't work... :( – Rothariger Oct 03 '18 at 23:16

2 Answers2

0

There is no need to write it using any writer, just use context.Result:

public class ApiExceptionFilterAttribute : ExceptionFilterAttribute
{
    public override void OnException(ExceptionContext context)
    {
        context.HttpContext.Response.StatusCode = (int)HttpStatusCode.InternalServerError;
        var ex = context.Exception;
        if (ex == null) return;

        context.Result = new JsonResult(ex);
    }
}
Alex Riabov
  • 8,655
  • 5
  • 47
  • 48
0

This should be fixed in Release 12.0.1 of Json.NET:

Change - Excluded TargetSite when serializing Exceptions without SerializableAttribute

The fix was made in commit Exclude TargetSite from non-serializable Exception serialization (#1897) on Oct 27, 2018.

The problem seems to have been introduced in Json.NET 11.0 Release 1:

Change - Types that implement ISerializable but don't have [SerializableAttribute] are not serialized using ISerializable

For discussion about why the change in 11.0.1 was originally made see this answer to Deserializing custom exceptions in Newtonsoft.Json.

dbc
  • 104,963
  • 20
  • 228
  • 340