如何使用Json格式化程序将抽象类传递给Asp.Net WebApi?

时间:2016-07-19 16:06:14

标签: c# json asp.net-web-api

基本上,我正在尝试使我的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.AutoWebApiConfig.cs放在一起,派生类可以正确反序列化和序列化。

0 个答案:

没有答案