Pregunta

I am evaluating ServiceStack at the moment. I am in need to create bunch of RESTful webservices. I have the initial code running, and I am quite happy with that. What I was struggling a bit was how to create a service that could consume POST (or PUT) HTTP request that has data in its body.

I've found this thread on ServiceStack forum (http://groups.google.com/group/servicestack/browse_thread/thread/693145f0c3033795) and folliwng it I've been guided to have a look at the following thread on SO (Json Format data from console application to service stack) but it was not really helpful - it described how to create a request, and not how to create a service that can consume such a HTTP request.

When I tried to pass additional data (in the HTTP message body) my servuce returned following error (HTTP 400):

<TaskResponse xmlns:i="http://www.w3.org/2001/XMLSchema-instance" xmlns="">
<ResponseStatus>
<ErrorCode>SerializationException</ErrorCode>
<Message>Could not deserialize 'application/xml' request using ServiceStackMVC.Task'
Error: System.Runtime.Serialization.SerializationException: Error in line 1 position 8.Expecting element 'Task' from namespace 'http://schemas.datacontract.org/2004/07/ServiceStackMVC'..    
Encountered 'Element'  with name 'Input', namespace ''. 
at System.Runtime.Serialization.DataContractSerializer.InternalReadObject(XmlReaderDelegator xmlReader, Boolean verifyObjectName, DataContractResolver dataContractResolver)
at System.Runtime.Serialization.XmlObjectSerializer.ReadObjectHandleExceptions XmlReaderDelegator reader, Boolean verifyObjectName, DataContractResolver dataContractResolver)
at System.Runtime.Serialization.XmlObjectSerializer.ReadObject(XmlDictionaryReader reader)
at System.Runtime.Serialization.XmlObjectSerializer.ReadObject(Stream stream)
at ServiceStack.Text.XmlSerializer.DeserializeFromStream(Type type, Stream stream) in  C:\src\ServiceStack.Text\src\ServiceStack.Text\XmlSerializer.cs:line 76
at ServiceStack.WebHost.Endpoints.Support.EndpointHandlerBase.CreateContentTypeRequest(IHttpRequest httpReq, Type requestType, String contentType) in C:\src\ServiceStack\src\ServiceStack\WebHost.Endpoints\Support\EndpointHandlerBase.cs:line 107</Message>
<StackTrace>   at ServiceStack.WebHost.Endpoints.Support.EndpointHandlerBase.CreateContentTypeRequest(IHttpRequest httpReq, Type requestType, String contentType) in C:\src\ServiceStack\src\ServiceStack\WebHost.Endpoints\Support\EndpointHandlerBase.cs:line 115
at ServiceStack.WebHost.Endpoints.RestHandler.GetRequest(IHttpRequest httpReq, IRestPath restPath) in C:\src\ServiceStack\src\ServiceStack\WebHost.Endpoints\RestHandler.cs:line 98
at ServiceStack.WebHost.Endpoints.RestHandler.ProcessRequest(IHttpRequest httpReq, IHttpResponse httpRes, String operationName) in C:\src\ServiceStack\src\ServiceStack\WebHost.Endpoints\RestHandler.cs:line 60</StackTrace>
</ResponseStatus>
</TaskResponse>

This led me to https://github.com/ServiceStack/ServiceStack/wiki/Serialization-deserialization I thought that I will give IRequiresRequestStream a go. At the moment my code is as follows:

public class Task : IRequiresRequestStream
{
    public string TaskName { get; set; }
    public string bodyData { get; set; }

    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();
    }

    public System.IO.Stream RequestStream
    {
        get
        {
            return new MemoryStream(System.Text.Encoding.UTF8.GetBytes(bodyData));
        }
        set
        {
            if (value.Length == 0)
            {
                bodyData = string.Empty;
            }
            else
            {
                byte[] buffer = new byte[value.Length];
                int bytesRead = value.Read(buffer, 0, (int)value.Length);
                bodyData = System.Text.Encoding.UTF8.GetString(buffer);
            }
        }
    }
}

and service itself:

public class TaskService : RestServiceBase<Task>
{
    public List<Task> tasks { get; set; }

    public override object OnGet(Task request)
    {
        if (string.IsNullOrEmpty(request.TaskName))
        {
            if (tasks == null || tasks.Count == 0)
                return "<tasks/>";
            StringBuilder sb = new StringBuilder();
            sb.AppendLine("<tasks>");
            foreach (Task t in tasks)
            {
                sb.AppendFormat("  <task id={0}><![CDATA[{2}]]><task/>{1}", t.TaskName, System.Environment.NewLine, t.bodyData);
            }
            sb.AppendLine("</tasks>");
            return sb.ToString();                
        }
        else
        {
            if (tasks.Contains(request))
            {
                var task = tasks.Where(t => t.TaskName == request.TaskName).SingleOrDefault();
                return String.Format("<task id={0}><![CDATA[{2}]]><task/>{1}", task.TaskName, System.Environment.NewLine, task.bodyData);
            }
            else
                return "<task/>";
        }
    }

    public override object OnPost(Task request)
    {
        if (tasks.Contains( request ))
        {
            throw new HttpError(System.Net.HttpStatusCode.NotModified, "additional information");
        }

        tasks.Add(new Task() { TaskName = request.TaskName, bodyData = request.bodyData });
        return null;
    }
}

My routes:

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

It works but... as I couldn't find any similar example I would like to ask if this is the correct way of creating a service that is capable of processing POST requests that have additional informaion included in their message body. Am I doing anything wrong? Is there anything that I've missed?

It was also mentioned on the SO thread link to which I have provided, that using DTO is the preferred way to pass data to ServiceStack based service. Assuming that client needs to send a lot of data, how could we achieve that? I don't want to pass data as JSON object in the URI. Am I making any false assumption here?


  1. Client is not going to be written in C# / .Net. Completely different technology is going to be used. This was one of the reasony why RESTful webservices.
  2. I know returning xml as string may not be the best idea. At the moment it is just a sample code. This will be changed later on.
  3. The most important part is if the solution provided for me is the proper way to create a webservice that can consume HTTP request that has xml data attached in its body. What I've shared with you works I am just not 100% sure that this is the best way to achieve my goal.

Edited on Thursday 8th of March, 2012:

After reading the answer and the comments I've changed my code a little bit. I was pretty sure that if I wanted to use serialization I had to use namespaces (when passing data in the HTTP message body to the service).

I've used http://localhost:53967/api/servicestack.task/xml/metadata?op=Task to get more information about the service I've created.

REST Paths:

All Verbs /tasks/{TaskName}
All Verbs /tasks

HTTP + XML: POST /xml/asynconeway/Task HTTP/1.1 Host: localhost Content-Type: application/xml Content-Length: length

<Task xmlns:i="http://www.w3.org/2001/XMLSchema-instance"   xmlns="http://schemas.datacontract.org/2004/07/ServiceStackMVC">
  <AuxData>String</AuxData>
  <TaskName>String</TaskName>
</Task>

What I wanted to check was if it was possible to "mix" REST URI and pass the rest of the data as an xml.

Using Fiddler, I've created following POST request:

POST http://localhost:53967/api/tasks/22

Request headers:

User-Agent: Fiddler
Host: localhost:53967
Content-Type: application/xml
Content-Length: 165

Request Body:

<Task xmlns:i="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://schemas.datacontract.org/2004/07/ServiceStackMVC">
  <AuxData>something</AuxData>
</Task>

My DTO right now is as follows:

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

    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();
    }
}

And my service code is:

public class TaskService : RestServiceBase<Task>
{
    public List<Task> tasks { get; set; }

    public override object OnGet(Task request)
    {
        return tasks;
    }

    public override object OnPost(Task request)
    {
        if (tasks.Contains( request ))
        {
            throw new HttpError(System.Net.HttpStatusCode.NotModified, "additional information");
        }

        tasks.Add(new Task() { TaskName = request.TaskName });
        return null;
    }
}

So is this the proper way of passing XML data to the service? I think I am quite happy with xml namespaces included - that makes it even easier to develop services.

¿Fue útil?

Solución

Nope, returning an xml string it's not the recommended approach since any string returned gets written directly to the response stream so the service will only work with XML services and not all the other endpoints.

The ServiceStack Way

Is to keep your DTOs that you define your web services with in their own largely dependency-free assembly (I generally will only have a reference the impl and dep-free ServiceStack.Interfaces.dll). You can then re-use these DTOs with ServiceStack's generic Service Clients to get a succinct, typed, end-to-end API without any code-gen.

Different built-in Service Clients

Your C#/.NET clients only need to use the Service Clients contained in the ServiceStack.Common NuGet package which just contains the ServiceStack.Text.dll, ServiceStack.Interfaces.dll and ServiceStack.Common.dll for full .NET and Silverlight 4/5 client builds.

ServiceStack.Common contains the following Service Clients:

  • JsonServiceClient - The lightweight, ubiquitous, self-describing resilient format
  • JsvServiceClient - Faster more compact than JSON ideal for .NET to .NET services
  • XmlServiceClient - For folks who like using XML (Slower than JSON/JSV)
  • Soap11ServiceClient / Soap12ServiceClient - If your company mandates the use of SOAP.

If you Install the ProtoBuf Format plugin you also have the option to use the ProtoBufServiceClient which is the fastest binary serializer for .NET.

Easy to swap, easy to test

The C# Service Clients share the same IServiceClient and IRestClient interfaces making it easy to swap out if you want to take advantage of a superior format. Here's an example taking advantage of this where the same Unit Test is also used as a JSON, XML, JSV and SOAP integration test.

By default out-of-the-box, ServiceStack makes all your services available via pre-defined routes in the following convention:

/api/[xml|json|html|jsv|csv]/[syncreply|asynconeway]/[servicename]

This is what the Service Clients use when you use the Send<TResponse> and SendAsync<TResponse> API methods which allows you to call your web services without having to define any custom routes, e.g:

var client = new JsonServiceClient();
var response = client.Send<TaskResponse>(new Task());

If you want you can use the Get, Post, Put, Delete API methods that allows you to specify a url so you can call web services using your custom routes, e.g:

Async API example

FilesResponse response;
client.GetAsync<FilesResponse>("files/", r => response = r, FailOnAsyncError);

Sync API example

var response = client.Get<FilesResponse>("files/README.txt");

Here are some Sync and Async API examples from the RestFiles example project.

XML and SOAP issues

Generally XML and SOAP are more strict and brittle compared to the other formats, to minimize interop issues and reduce payload bloat you should set a global XML Namespace for all your DTOs by adding an Assembly attribute in your DTO Assembly.cs file, e.g:

[assembly: ContractNamespace("http://schemas.servicestack.net/types", 
    ClrNamespace = "MyServiceModel.DtoTypes")]

If you want to use a different ContractNamespace than the above you will also need to also set it in the EndpointHostConfig.WsdlServiceNamespace if you wish to make use of the SOAP endpoints.

Here are some more versioning tips when developing SOAP/XML web services: https://groups.google.com/d/msg/servicestack/04GQLsQ6YB4/ywonWgD2WeAJ

SOAP vs REST

Since SOAP routes all requests through the HTTP POST verb, if you wish to make each service available via SOAP as well you will need to create a new class per service and define custom REST-ful routes to each service as described here.

Due to the brittleness, bloated payload size and slower perf of SOAP/XML, it is recommended to use either the JSON, JSV or ProtoBuf formats/endpoints.

Request Model Binders

Another alternative to using IRequiresRequestStream is to use Request Model Binders you can define in your AppHost, e.g:

base.RequestBinders[typeof(Task)] = httpReq => ... requestDto;

C# Client Recommendation

The recommendation is to use ServiceStack's built-in service clients for C# clients although if you wish to use your own HttpClient, than still using the XmlServiceClient will come in handy as you can use Fiddler to see the exact wire-format ServiceStack expects.

Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top