我正在评估ServiceStack。我需要创建一堆RESTful webservices。我有初始代码运行,我很满意。我正在努力的是如何创建一个可以消耗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消息体中),我的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>
这导致我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对象传递。我在这里做出任何错误的假设吗?
在阅读了答案和评论之后,我改变了我的代码。我很确定如果我想使用序列化,我必须使用命名空间(当将HTTP消息体中的数据传递给服务时)。
我使用http://localhost:53967/api/servicestack.task/xml/metadata?op=Task
来获取有关我创建的服务的更多信息。
REST路径:
All Verbs /tasks/{TaskName}
All Verbs /tasks
HTTP + XML: POST / xml / asynconeway /任务HTTP / 1.1 主持人:localhost Content-Type:application / 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名称空间非常满意 - 这使得开发服务变得更加容易。
答案 0 :(得分:9)
不,返回一个xml字符串,这不是推荐的方法,因为返回的任何字符串都直接写入响应流,因此该服务只能用于XML服务而不是所有其他端点。
是否要将您定义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包含以下服务客户端:
如果您安装ProtoBuf Format插件,您还可以选择使用 ProtoBufServiceClient ,它是.NET中最快的二进制序列化程序。
C#服务客户端共享相同的IServiceClient
和IRestClient
接口,如果您想利用优质格式,可以轻松换出。 Here's an example利用此功能,相同的单元测试 也用作JSON,XML,JSV和SOAP集成测试。
默认情况下,ServiceStack默认使用pre-defined routes在以下约定中提供所有服务:
/api/[xml|json|html|jsv|csv]/[syncreply|asynconeway]/[servicename]
这是服务客户端在您使用Send<TResponse>
和SendAsync<TResponse>
API方法时使用的方法,这些方法允许您调用您的网络服务,而无需定义任何自定义路线,例如:
var client = new JsonServiceClient();
var response = client.Send<TaskResponse>(new Task());
如果您需要,可以使用Get,Post,Put,Delete API方法来指定网址,以便您可以使用自定义路由调用Web服务,例如:
FilesResponse response;
client.GetAsync<FilesResponse>("files/", r => response = r, FailOnAsyncError);
var response = client.Get<FilesResponse>("files/README.txt");
以下是Sync中的一些Async和RestFiles example project API示例。
与其他格式相比,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通过HTTP POST动词路由所有请求,如果您希望通过SOAP提供每个服务,则需要为每个服务创建一个新类,并定义每个服务的自定义REST-ful路由{{3 }}
由于脆弱性,膨胀的有效负载大小和较慢的SOAP / XML性能,建议使用JSON,JSV或ProtoBuf格式/端点。
使用IRequiresRequestStream
的另一种方法是使用您可以在AppHost中定义的请求模型绑定器,例如:
base.RequestBinders[typeof(Task)] = httpReq => ... requestDto;
建议使用ServiceStack的C#客户端内置服务客户端,但如果您希望使用自己的HttpClient,那么使用XmlServiceClient将会派上用场,因为您可以使用Fiddler查看确切的有线格式ServiceStack预期。