2

I have a problem testing webservice that has its own de/serialization mechanism provided.

My sample Task class that is being used by TaskService:

public class Task
{
    public string TaskName { get; set; }
    public string AuxData { get; set; }

    public static void RegisterCustomSerialization(IAppHost appHost)
    {
        appHost.ContentTypeFilters.Register("application/xml", SerializeTaskToStream, DeserializeTaskFromStream);
    }

    public static void SerializeTaskToStream(IRequestContext requestContext, object response, Stream stream)
    {
        var tasks = response as List<Task>;
        if (tasks != null)
        {
            using (var sw = new StreamWriter(stream))
            {
                if (tasks.Count == 0)
                {
                    sw.WriteLine("<Tasks/>");
                    return;
                }

                sw.WriteLine("<Tasks>");
                foreach (Task task in tasks)
                {
                    if (task != null)
                    {
                        sw.WriteLine("  <Task type=\"new serializer\">");
                        sw.Write("    <TaskName>");
                        sw.Write(task.TaskName);
                        sw.WriteLine("</TaskName>");
                        sw.Write("    <AuxData>");
                        sw.Write(task.AuxData);
                        sw.WriteLine("</AuxData>");
                        sw.WriteLine("  </Task>");
                    }
                }
                sw.WriteLine("</Tasks>");
            }
        }
        else
        {
            var task = response as Task;
            using (var sw = new StreamWriter(stream))
            {
                if (task != null)
                {
                    sw.WriteLine("  <Task type=\"new serializer\">");
                    sw.Write("    <TaskName>");
                    sw.Write(task.TaskName);
                    sw.WriteLine("</TaskName>");
                    sw.Write("    <AuxData>");
                    sw.Write(task.AuxData);
                    sw.WriteLine("</AuxData>");
                    sw.WriteLine("  </Task>");
                }
            }
        }
    }

    public static object DeserializeTaskFromStream(Type type, Stream stream)
    {
        if (stream == null || stream.Length == 0)
            return null; // should throw exception?
        XDocument xdoc = XDocument.Load(stream);
        XElement auxData = xdoc.Root.Element("AuxData");

        return new Task() { AuxData = auxData.Value };
    }


    public override bool Equals(object obj)
    {
        Task task = obj as Task;
        if (task == null)
            return false;
        return TaskName.Equals(task.TaskName);
    }

    public override int GetHashCode()
    {
        return TaskName.GetHashCode();
    }
}

I have based my serialization / deserialization code on: http://www.servicestack.net/ServiceStack.Northwind/vcard-format.htm and https://github.com/ServiceStack/ServiceStack.Examples/blob/master/src/ServiceStack.Northwind/ServiceStack.Northwind.ServiceInterface/VCardFormat.cs

My base test class is as follows:

public class SimpleRestTestBase : AppHostBase
{
    public SimpleRestTestBase() : base( "SimpleRestTestBase", typeof(TaskService).Assembly)
    {
        Instance = null;
        Init();
    }

    public override void Configure(Funq.Container container)
    {
        SetConfig(new EndpointHostConfig
        {
            DefaultContentType = ContentType.Xml
        }
        );

        Task.RegisterCustomSerialization(this);

        Routes
          .Add<Task>("/tasks/{TaskName}")
          .Add<List<Task>>("/tasks");

        container.Register(new List<Task>());
    }
}

And the unit test that fails:

[TestFixture]
public class SimpleTest : SimpleRestTestBase
{
    [Test]
    public void TestMetodRequiringServer()
    {
        var client = (IRestClient)new XmlServiceClient("http://localhost:53967");
        var data = client.Get<List<Task>>("/api/tasks");
    }
}

The exception I get when using nUnit test runner is:

Testing.SimpleTest.TestMetodRequiringServer: System.Runtime.Serialization.SerializationException : Error in line 1 position 9. Expecting element 'ArrayOfTask' from namespace 'http://schemas.datacontract.org/2004/07/ServiceStackMVC'.. Encountered 'Element' with name 'Tasks', namespace ''.

How do I pass information about my custom serialization/deseialization code to the XmlServiceClient?

Maciek Talaska
  • 1,628
  • 1
  • 14
  • 22

1 Answers1

2

You're overriding the generic XML Serialization format (application/xml) with a custom version that is strongly-coupled to only handle 1 web service output - this is very rarely what you want since it will prevent (i.e. break) all your other services from returning XML. If you want to return custom XML, just limit to the services that need it by returning a xml string instead.

You can't change the implementation of XmlServiceClient as it is strongly coupled to the XML Serialization/DeSerialization that ServiceStack uses. You should use a raw HTTP Client to send the exact XML payload you want. Here's an example sending raw XML with .NET's web request: https://stackoverflow.com/a/8046734/85785

Since you're returning and sending custom XML you may also want to override the Custom Request Binder for your web service so you have an opportunity to deserialize the request how you want.

See the wiki page below for some examples on how to do this:

https://github.com/ServiceStack/ServiceStack/wiki/Serialization-deserialization

Note: returning custom XML is not ideal since it by-passes many of the advantages of ServiceStack's strong-typed, intelligent and opinionated nature.

Community
  • 1
  • 1
mythz
  • 141,670
  • 29
  • 246
  • 390
  • Thank you for pointing out that appHost.ContentTypeFilters.Register affects all services consuming / returning HTTP messages with Content-Type set to "application/xml" (this is how I understand it right now). CustomRequestBinder allows me only to customize serialization, right? How about controlling the way data is serialized? – Maciek Talaska Mar 13 '12 at 12:11
  • Request Binder allows for custom **de-serialization** see http://stackoverflow.com/questions/6245616/does-servicestack-support-binary-responses for examples of different return types. i.e. you can just return a string, otherwise you can add your own ResponseFilter to write directly to the response see: https://github.com/ServiceStack/ServiceStack/wiki/Request-and-response-filters – mythz Mar 13 '12 at 18:32