1

I thought I had jumped through the necessary hoops to get my JsonMediaTypeFormatter working with custom ISerializable implementations, complete with passing unit tests. But I'm unable to get it to work when I pass in values via Swagger UI.

My key questions are:

  1. What am I doing wrong with my unit test causing it to serialize/deserialize different from what Web API is doing?
  2. What do I need to change to get this working with Web API's serializing/deserialization and Swagger/Swashbuckle?

Class being serialized: (Notice that serializing and then deserializing drops off the time component and only keeps the date component. The helps for testing/observing purposes.)

public class Pet : ISerializable
{
    public DateTime Dob { get; set; }

    public Pet()
    {
        Dob = DateTime.Parse("1500-12-25 07:59:59");
    }

    public Pet(SerializationInfo info, StreamingContext context)
    {
        Dob = DateTime.Parse(info.GetString("Dob"));
    }

    public void GetObjectData(SerializationInfo info, StreamingContext context)
    {
        info.AddValue("Dob", Dob.Date.ToString());
    }
}

Web API Method: (always returns null)

public class TestController : ApiController
{
    [Route("~/api/Pet")]
    public string Get([FromUri] Pet data)
    {
        return data.Dob.ToString();
    }
}

Passing Unit Test: (and serialization helpers from MSDN docs)

[TestFixture]
public class SerializationTests
{
    [Test]
    public void PetTest()
    {
        var date = new DateTime(2017, 1, 20, 5, 0, 0);
        var foo = new Pet { Dob = date };

        var jsonFormatter = new JsonMediaTypeFormatter { SerializerSettings = new JsonSerializerSettings { ContractResolver = new DefaultContractResolver { IgnoreSerializableInterface = false } } };
        var serialized = SerializationHelpers.Serialize(jsonFormatter, foo);

        Console.WriteLine(serialized);
        var deserialized = SerializationHelpers.Deserialize<Pet>(jsonFormatter, serialized);

        Assert.That(foo.Dob, Is.Not.EqualTo(date.Date));
        Assert.That(deserialized.Dob, Is.EqualTo(date.Date));
    }
}

public static class SerializationHelpers
{
    public static string Serialize<T>(MediaTypeFormatter formatter, T value)
    {
        // Create a dummy HTTP Content.
        Stream stream = new MemoryStream();
        var content = new StreamContent(stream);
        // Serialize the object.
        formatter.WriteToStreamAsync(typeof(T), value, stream, content, null).Wait();
        // Read the serialized string.
        stream.Position = 0;
        return content.ReadAsStringAsync().Result;
    }

    public static T Deserialize<T>(MediaTypeFormatter formatter, string str) where T : class
    {
        // Write the serialized string to a memory stream.
        Stream stream = new MemoryStream();
        StreamWriter writer = new StreamWriter(stream);
        writer.Write(str);
        writer.Flush();
        stream.Position = 0;
        // Deserialize to an object of type T
        return formatter.ReadFromStreamAsync(typeof(T), stream, null, null).Result as T;
    }
}

WebApiConfig.cs

public static class WebApiConfig
{
    public static void Register(HttpConfiguration config)
    {
        // Web API configuration and services
        config.Formatters.Clear();
        var jsonFormatter = new JsonMediaTypeFormatter { SerializerSettings = new JsonSerializerSettings { ContractResolver = new DefaultContractResolver { IgnoreSerializableInterface = false } } };
        config.Formatters.Add(jsonFormatter);

        // Web API routes
        config.MapHttpAttributeRoutes();

        config.Routes.MapHttpRoute(
            name: "DefaultApi",
            routeTemplate: "api/{controller}/{id}",
            defaults: new { id = RouteParameter.Optional }
        );
    }
}

A few other notes:

  1. When I run the passing unit test, the Console.WriteLine output is:

{"Dob":"1/20/2017 12:00:00 AM"}

which is exactly what I want/expect.

  1. My Swagger UI looks like this using the default Swashbuckle settings from Nuget. Note that value of the date is what is set in the default constructor, showing that my ISerializable implementation is ignored.

Basic Deserialization Results

NOTE: I have changed the question to remove all generics from the picture. This problem is fundamentally about ISerializable implementations now and not about Generics.

Jaxidian
  • 13,081
  • 8
  • 83
  • 125
  • I think the issue here is with the use of generics on your GET request model - `public string Get([FromUri] MyClass data)`. MyClass model cannot be a generic. Try making that parameter a concrete type. – alltej Mar 09 '17 at 06:20
  • @alltej I can definitely get simple POCO classes to serialize correctly. Why are generics a problem? I have tried using a custom `JsonMediaTypeFormatter` (which works in unit tests) but the exactly same `JsonMediaTypeFormatter` fails when Web API invokes it. Why can I not leverage my custom-defined serialization? All of the blogs and MSDN docs say I can use custom serialization. – Jaxidian Mar 09 '17 at 13:41

1 Answers1

0

WebAPI api does not know how to deserialize this generic object. I see a similar question here in SO but did not personally try/test it. Hope it helps: Generic Web Api method

Rather than having a generic method, you can create a generic controller. So your code above will look something like below.

public abstract class MyClass{ }

public class PersonDto: MyClass{}


public class TestController<T> : ApiController where T: MyClass
{
    public string Get([FromUri] T data)
    {
        ...
    }
}
Community
  • 1
  • 1
alltej
  • 6,787
  • 10
  • 46
  • 87
  • 1
    Based on your input, I have found that I can simplify the problem. I have edited the question heavily and now generics are now longer part of the problem. The problem is completely about Web API and `ISerializable` implementations. This should confirm that generics are not the fundamental problem here. – Jaxidian Mar 09 '17 at 15:11