问题
Assume TypeNameHandling.Auto
is used for Json.net in the following Web Api Controllers:
class A {}
class B : A {}
class FooController : ApiController
{
public A Get() {
return new A();
}
}
class BarController : ApiController
{
public A Get() {
return new B();
}
}
Then I would expect the resulting Json to be:
Foo
{}
Bar
{'$type':...}
However, the output of Bar is also {}
. On the other hand, if the API controller returned IEnumerable<A>
and we returned a lot of B
's then the type property is set.
Is it possible to change this behaviour such that it uses the return type as input to Json.Net?
A fix is to return the Json and not the object but I find that a dissatisfactory solution.
回答1:
Thanks for this scenario as I think this is something which should be included by default in our Json formatter. Following is a custom Json formatter where I try to pass in the 'type' information to the Serialize
method of JsonSerializer
. I tried the below custom formatter with your scenario and it seemed to work fine.
(Most of the code below was cherry picked from existing Web API source code to suit your scenario.)
public class CustomJsonFormatter : JsonMediaTypeFormatter
{
public override Task WriteToStreamAsync(Type type, object value, Stream writeStream, HttpContent content, TransportContext transportContext)
{
try
{
Encoding effectiveEncoding = SelectCharacterEncoding(content == null ? null : content.Headers);
if (!UseDataContractJsonSerializer)
{
using (JsonTextWriter jsonTextWriter = new JsonTextWriter(new StreamWriter(writeStream, effectiveEncoding)) { CloseOutput = false })
{
if (Indent)
{
jsonTextWriter.Formatting = Newtonsoft.Json.Formatting.Indented;
}
JsonSerializer jsonSerializer = JsonSerializer.Create(this.SerializerSettings);
jsonSerializer.Serialize(jsonTextWriter, value, type); //NOTE: passing in 'type' here
jsonTextWriter.Flush();
}
}
else
{
return base.WriteToStreamAsync(type, value, writeStream, content, transportContext);
}
return TaskHelpers.Completed();
}
catch (Exception e)
{
return TaskHelpers.FromError(e);
}
}
}
internal class TaskHelpers
{
private static readonly Task _defaultCompleted = FromResult<AsyncVoid>(default(AsyncVoid));
/// <summary>
/// Used as the T in a "conversion" of a Task into a Task{T}
/// </summary>
private struct AsyncVoid
{
}
internal static Task<TResult> FromResult<TResult>(TResult result)
{
TaskCompletionSource<TResult> tcs = new TaskCompletionSource<TResult>();
tcs.SetResult(result);
return tcs.Task;
}
/// <summary>
/// Returns an error task. The task is Completed, IsCanceled = False, IsFaulted = True
/// </summary>
internal static Task FromError(Exception exception)
{
return FromError<AsyncVoid>(exception);
}
/// <summary>
/// Returns an error task of the given type. The task is Completed, IsCanceled = False, IsFaulted = True
/// </summary>
/// <typeparam name="TResult"></typeparam>
internal static Task<TResult> FromError<TResult>(Exception exception)
{
TaskCompletionSource<TResult> tcs = new TaskCompletionSource<TResult>();
tcs.SetException(exception);
return tcs.Task;
}
/// <summary>
/// Returns a completed task that has no result.
/// </summary>
internal static Task Completed()
{
return _defaultCompleted;
}
}
回答2:
I had to change Kiran's solution with the following such that arrays etc. will be written as [...]
instead of {$type: IEnumerab...., values = ...}
.
var contract = jsonSerializer.ContractResolver.ResolveContract(type);
// Only use the declared type if it is an object contract such that
// arrays, dictionaries, etc. aren't affected.
if (contract is JsonObjectContract)
{
jsonSerializer.Serialize(jsonTextWriter, value, type); // NOTE: passing in 'type' here
}
else
{
jsonSerializer.Serialize(jsonTextWriter, value);
}
来源:https://stackoverflow.com/questions/20167895/json-net-typenamehandling-auto-and-asp-web-api-controller-gives-unexpected-behav