基本上,我正在尝试使我的WebApi更通用:
型号:
public abstract class A {}
public class B : A { }
客户端:
using (var client = new HttpClient())
{
client.BaseAddress = new Uri("http://localhost:49611/");
var aList = List<A> { new B() };
var response = await client.PostAsJsonAsync("api/a", aList);
}
WebApi控制器:
public class AController : ApiController
{
public void Post([FromBody]List<A> aList)
{
// ...
}
}
尝试执行此代码,我在AController中得到一个空列表(因为A是抽象的)。
如果我为A类删除“abstract”,那么我在aList中得到一个项目,类型是A,而不是B,正如我所料。
从我用Google搜索,我认为,TypeNameHandling.Auto
应该是解决方案
public static class WebApiConfig
{
public static void Register(HttpConfiguration config)
{
//...
config.Formatters.JsonFormatter.SerializerSettings.TypeNameHandling = Newtonsoft.Json.TypeNameHandling.Auto;
}
}
然而,它对我不起作用。
但是,作为测试,如果我直接使用JsonConverter,我确实看到TypeNameHandling.Auto
正在运行:
var aList = List<A> { new B() };
var settings = new JsonSerializerSettings { TypeNameHandling = TypeNameHandling.Auto };
var str = JsonConvert.SerializeObject(aList, Formatting.Indented, settings);
var obj = JsonConvert.DeserializeObject<List<A>>(str, settings);
在这种情况下,我确实在obj中获得了B(而不是A)的列表。
那么,为什么它不能用于我的WebApi?
我发现这个post已经超过两年了,他说“{API}不会使用public void Serialize(JsonWriter jsonWriter, object value, Type objectType);
的过载”。两年后,我无法相信它仍然是真的。无论如何,在该帖子的公认答案中,Kiran提供了一个自定义的json格式化程序,它应该可以解决这个问题。但是,当我尝试将其插入我的WebApiConfig
时,它对我不起作用。实际上,被覆盖的WriteToStreamAsync
根本没有被调用 - 真的很奇怪。
这是Kiran的代码:
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;
}
}
这是我的WebApiConfig:
public static class WebApiConfig
{
public static void Register(HttpConfiguration config)
{
// Web API configuration and services
// Configure Web API to use only bearer token authentication.
config.SuppressDefaultHostAuthentication();
config.Filters.Add(new HostAuthenticationFilter(OAuthDefaults.AuthenticationType));
// Web API routes
config.MapHttpAttributeRoutes();
config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{id}",
defaults: new { id = RouteParameter.Optional }
);
config.formatters.clear();
config.Formatters.Add(new CustomJsonFormatter() { SerializerSettings = new JsonSerializerSettings() { TypeNameHandling = TypeNameHandling.Auto } });
}
}
有人有线索吗?
更新和解决方案:
好吧,经过一番挣扎,我自己想出来了。问题出在使用clien.PostAsJsonAsync
的客户端请求中。我必须使用另一个带有formatter参数的函数PostAsync
来添加TypeNameHandling.Auto
,如下所示。
var formatter = new JsonMediaTypeFormatter() { SerializerSettings = new JsonSerializerSettings() { TypeNameHandling = TypeNameHandling.Auto } };
var response = await client.PostAsync("api/a", aList, formatter);
将TypeNameHandling.Auto
和WebApiConfig.cs
放在一起,派生类可以正确反序列化和序列化。