【问题标题】:ServiceStack RESTful WebService and passing data in message bodyServiceStack RESTful WebService 并在消息体中传递数据
【发布时间】:2012-03-08 19:09:36
【问题描述】:

我目前正在评估 ServiceStack。我需要创建一堆 RESTful 网络服务。我运行了初始代码,对此我感到非常满意。我有点挣扎的是如何创建一个可以使用 POST(或 PUT)HTTP 请求的服务,该请求在其正文中包含数据。

我在 ServiceStack 论坛 (http://groups.google.com/group/servicestack/browse_thread/thread/693145f0c3033795) 上找到了这个帖子,然后我被引导查看 SO (Json Format data from console application to service stack) 上的以下帖子,但它并没有真正的帮助 - 它描述了如何来创建请求,而不是如何创建可以使用此类 HTTP 请求的服务。

当我尝试传递其他数据(在 HTTP 消息正文中)时,我的服务返回以下错误 (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>

这让我去了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);
            }
        }
    }
}

和服务本身:

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

我的路线:

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

它可以工作,但是...因为我找不到任何类似的示例,所以我想问一下,这是否是创建能够处理消息正文中包含附加信息的 POST 请求的服务的正确方法。我做错什么了吗?有什么我错过的吗?

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


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

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

在阅读了答案和 cmets 之后,我稍微更改了我的代码。我很确定,如果我想使用序列化,我必须使用命名空间(将 HTTP 消息正文中的数据传递给服务时)。

我使用http://localhost:53967/api/servicestack.task/xml/metadata?op=Task 来获取有关我创建的服务的更多信息。

REST 路径:

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

HTTP + XML: POST /xml/asynconeway/Task HTTP/1.1 主机:本地主机 内容类型:应用程序/xml 内容长度:长度

<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>

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

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

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

请求标头:

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

请求正文:

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

我现在的 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();
    }
}

而我的服务代码是:

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

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

【问题讨论】:

  • 我会添加程序集属性以消除自动生成的 'schemas.datacontract.org...' 并让所有 DTO 共享相同的命名空间。 NotModified (304) 也不是 HTTP 错误代码 (>=400)。对于幂等性问题,我只会使用 NoConflict(409)。你也可以返回一个 HttpError ,它与抛出它做同样的事情,只是节省了一点性能 - 当我在服务类中返回 HttpError 时,我会返回。

标签: c# servicestack


【解决方案1】:

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

ServiceStack 方式

将您定义 Web 服务的 DTO 保留在 他们自己的 很大程度上无依赖的程序集 (我通常只会参考 impl 和无 dep 的 ServiceStack.Interfaces .dll)。然后,您可以重用这些 DTO 与 ServiceStack 的通用服务客户端,以获得简洁、类型化、端到端的 API,无需任何代码生成。

不同的内置服务客户端

您的 C#/.NET 客户端只需要使用包含在 ServiceStack.Common NuGet 包,其中仅包含用于完整 .NET 和 Silverlight 4/5 客户端构建的 ServiceStack.Text.dll、ServiceStack.Interfaces.dll 和 ServiceStack.Common.dll。

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

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

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

易于更换,易于测试

C# 服务客户端共享相同的IServiceClientIRestClient 接口,如果您想利用更高级的格式,可以轻松换出。 Here's an example 利用这一点,相同的单元测试用作 JSON、XML、JSV 和 SOAP 集成测试。

默认情况下,ServiceStack 按照以下约定通过pre-defined routes 提供所有服务:

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

这是服务客户端在您使用 Send&lt;TResponse&gt;SendAsync&lt;TResponse&gt; API 方法时所使用的,它允许您调用您的网络服务不必定义任何自定义路由,例如:

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

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

异步 API 示例

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

同步 API 示例

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

以下是来自RestFiles example project 的一些SyncAsync API 示例。

XML 和 SOAP 问题

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

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

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

以下是开发 SOAP/XML Web 服务时的一些版本控制技巧: https://groups.google.com/d/msg/servicestack/04GQLsQ6YB4/ywonWgD2WeAJ

SOAP 与 REST

由于 SOAP 通过 HTTP POST 动词路由所有请求,如果您也希望通过 SOAP 使每个服务可用,则需要为每个服务创建一个新类,并将每个服务的自定义 REST-ful 路由定义为 described here .

由于 SOAP/XML 的脆弱性、负载过大以及性能较慢,建议使用 JSON、JSV 或 ProtoBuf 格式/端点。

请求模型绑定器

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

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

C# 客户端推荐

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

【讨论】:

  • 感谢您的回答。客户端不会用 C# 编写(我们将使用完全不同的语言 - 这就是我们决定使用 RESTful Web 服务的原因)。对我来说最重要的是确保它是创建可以在消息正文中传递 xml 数据的服务的正确方法。我们不能在 uri 中传递所有数据。我知道将 xml 作为字符串返回不是一种选择,但目前出于某种原因我想保持原样。
  • 注意servicestack.net 上的所有演示项目只使用 jQuery,其中 JSON 仍然是首选选项 :)
  • 因此假设由于某种原因客户端应用程序需要在消息正文(HTTP 请求)中发送数据,是否要实现 IRequiresRequestStream?现有设计无法更改,需要在 HTTP 请求中发送 xml(创建和更新对象时)。
  • 老实说,我只会将 IRequiresRequestStream 用于不映射到 C# DTO 的外部请求。只需让客户端发送正确的 XML ServiceStack 期望 - 从长远来看,它的摩擦要少得多。您可以使用 /api/metadata 向您展示服务堆栈期望的有效负载示例。
  • 感谢您的所有帮助。我对代码进行了一些更改,想问一下我现在所做的是否正确?
猜你喜欢
  • 1970-01-01
  • 2016-10-20
  • 2013-06-06
  • 2014-08-23
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2017-03-23
  • 1970-01-01
相关资源
最近更新 更多