ServiceStack RESTful WebService并在消息体中传递数据

Mac*_*ska 5 c# servicestack

我目前正在评估ServiceStack.我需要创建一堆RESTful webservices.我有初始代码运行,我很满意.我正在努力的是如何创建一个可以消耗POST(或PUT)HTTP请求的服务,该请求在其体内有数据.

我已经在ServiceStack论坛(http://groups.google.com/group/servicestack/browse_thread/thread/693145f0c3033795)上找到了这个帖子,并且我已经被引导去看看SO上的以下帖子(Json)将数据从控制台应用程序格式化到服务堆栈,但它确实没有用 - 它描述了如何创建请求,而不是如何创建可以使用此类HTTP请求的服务.

当我尝试传递其他数据时(在HTTP消息体中),我的servuce返回以下错误(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>
Run Code Online (Sandbox Code Playgroud)

这导致我https://github.com/ServiceStack/ServiceStack/wiki/Serialization-deserialization我以为我会IRequiresRequestStream放手一搏.目前我的代码如下:

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);
            }
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

和服务本身:

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;
    }
}
Run Code Online (Sandbox Code Playgroud)

我的路线:

Routes.Add<Task>("/tasks/{TaskName}").Add<Task>("/tasks");
Run Code Online (Sandbox Code Playgroud)

它工作但是......因为我找不到任何类似的例子我想问这是否是创建一个能够处理POST请求的服务的正确方法,这些POST请求在其消息体中包含了额外的信息.我做错了吗?有什么我错过的吗?

在我提供的SO线程链接上也提到过,使用DTO是将数据传递给基于ServiceStack的服务的首选方法.假设客户需要发送大量数据,我们怎么能实现呢?我不想在URI中将数据作为JSON对象传递.我在这里做出任何错误的假设吗?


  1. 客户端不会用C#/ .Net编写.将使用完全不同的技术.这是为什么RESTful webservices的原因之一.
  2. 我知道将xml作为字符串返回可能不是最好的主意.目前它只是一个示例代码.这将在以后更改.
  3. 最重要的部分是,如果为我提供的解决方案是创建可以使用在其正文中附加xml数据的HTTP请求的Web服务的正确方法.我与你分享的作品我不是100%确定这是实现我的目标的最佳方式.

2012年3月8日星期四编辑:

在阅读了答案和评论之后,我改变了我的代码.我很确定如果我想使用序列化,我必须使用命名空间(当将HTTP消息体中的数据传递给服务时).

我以前已经http://localhost:53967/api/servicestack.task/xml/metadata?op=Task获得了有关我创建的服务的更多信息.

REST路径:

All Verbs /tasks/{TaskName}
All Verbs /tasks
Run Code Online (Sandbox Code Playgroud)

HTTP + XML:POST/xml/asynconeway /任务HTTP/1.1主机:localhost内容类型: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>
Run Code Online (Sandbox Code Playgroud)

我想检查的是,是否可以"混合"REST URI并将其余数据作为xml传递.

使用Fiddler,我创建了以下POST请求:

POST http://localhost:53967/api/tasks/22
Run Code Online (Sandbox Code Playgroud)

请求标头:

User-Agent: Fiddler
Host: localhost:53967
Content-Type: application/xml
Content-Length: 165
Run Code Online (Sandbox Code Playgroud)

请求机构:

<Task xmlns:i="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://schemas.datacontract.org/2004/07/ServiceStackMVC">
  <AuxData>something</AuxData>
</Task>
Run Code Online (Sandbox Code Playgroud)

我的DTO现在如下:

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();
    }
}
Run Code Online (Sandbox Code Playgroud)

我的服务代码是:

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;
    }
}
Run Code Online (Sandbox Code Playgroud)

那么这是将XML数据传递给服务的正确方法吗?我认为我对包含的xml命名空间感到非常满意 - 这使得开发服务变得更加容易.

myt*_*thz 9

不,返回一个xml字符串,这不是推荐的方法,因为返回的任何字符串都直接写入响应流,因此该服务只能用于XML服务,而不能用于所有其他端点.

ServiceStack方式

是保持您定义Web服务的DTO在他们自己的基本上无依赖的程序集中(我通常只会引用impl和dep-free ServiceStack.Interfaces.dll).然后,您可以重新使用这些用的DTO ServiceStack的通用服务客户端得到一个简洁,类型,终端到终端的API没有任何代码生成.

不同的内置服务客户端

您的C#/ .NET客户端只需要使用ServiceStack.Common NuGet包中包含的服务客户端, 它包含ServiceStack.Text.dll,ServiceStack.Interfaces.dll和ServiceStack.Common.dll,用于完整的.NET和Silverlight 4/5个客户端构建.

ServiceStack.Common包含以下服务客户端:

  • JsonServiceClient - 轻量级,无处不在,自描述的弹性格式
  • JsvServiceClient - 比用于.NET到.NET服务的JSON更加紧凑
  • XmlServiceClient - 对于喜欢使用XML的人(比JSON/JSV慢)
  • Soap11ServiceClient/Soap12ServiceClient - 如果您的公司要求使用SOAP.

如果您安装ProtoBuf Format插件,您还可以选择使用ProtoBufServiceClient,这是.NET最快的二进制序列化程序.

易于更换,易于测试

C#的服务客户端共享同IServiceClientIRestClient接口使它容易掉出来,如果你想利用优越的格式的优势.这里有一个例子 借这个地方的优势,同单元测试用作JSON,XML,JSV和SOAP集成测试.

默认情况下,ServiceStack可以通过以下约定通过预定义的路由提供所有服务:

/api/[xml|json|html|jsv|csv]/[syncreply|asynconeway]/[servicename]
Run Code Online (Sandbox Code Playgroud)

这是服务客户端使用Send<TResponse>SendAsync<TResponse>API方法时使用的方法,它允许您调用Web服务无需定义任何自定义路由,例如:

var client = new JsonServiceClient();
var response = client.Send<TaskResponse>(new Task());
Run Code Online (Sandbox Code Playgroud)

如果需要,您可以使用Get,Post,Put,Delete API方法,这些方法允许您指定URL,以便您可以使用自定义路由调用Web服务,例如:

异步API示例

FilesResponse response;
client.GetAsync<FilesResponse>("files/", r => response = r, FailOnAsyncError);
Run Code Online (Sandbox Code Playgroud)

Sync API示例

var response = client.Get<FilesResponse>("files/README.txt");
Run Code Online (Sandbox Code Playgroud)

以下是RestFiles示例项目中的一些SyncAsync API示例.

XML和SOAP问题

通常,XML和SOAP与其他格式相比更严格和更脆弱,为了最小化互操作问题并减少有效负载膨胀,您应该通过在DTO Assembly.cs文件中添加Assembly属性为所有DTO设置全局XML命名空间,例如:

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

如果要使用与上述不同的ContractNamespace,EndpointHostConfig.WsdlServiceNamespace如果您希望使用SOAP端点,还需要在其中设置它.

下面是一些提示版本开发SOAP/XML Web服务时: https://groups.google.com/d/msg/servicestack/04GQLsQ6YB4/ywonWgD2WeAJ

SOAP与REST

由于SOAP通过HTTP POST动词路由所有请求,如果您希望通过SOAP提供每个服务,则需要为每个服务创建一个新类,并为每个服务定义自定义REST-ful路由,如此处所述.

由于脆弱性,膨胀的有效负载大小和较慢的SOAP/XML性能,建议使用JSON,JSV或ProtoBuf格式/端点.

请求型号粘合剂

使用的另一种方法IRequiresRequestStream是使用您可以在AppHost中定义的请求模型绑定器,例如:

base.RequestBinders[typeof(Task)] = httpReq => ... requestDto;
Run Code Online (Sandbox Code Playgroud)

C#客户端推荐

建议使用ServiceStack的C#客户端内置服务客户端,但如果您希望使用自己的HttpClient,那么仍然可以使用XmlServiceClient,因为您可以使用Fiddler来查看ServiceStack所期望的确切线程格式.

  • 老实说,我只使用IRequiresRequestStream来处理未映射到C#DTO的外部请求.只是让客户端发送正确的XML ServiceStack预期 - 从长远来看,它的摩擦力要小得多.您可以使用/ api/metadata来显示有效负载servicestack期望的示例. (2认同)