4

我有一个使用 ServiceStack 的简单 API 设置。我使用以下代码使其运行:

namespace TheGuest.Test
{
    [DataContract]
    [Description("A sample web service.")]
    public class Greet
    {
        [DataMember]
        public string Name { get; set; }
    }

    [DataContract]
    public class GreetResponse
    {
        [DataMember]
        public string Result { get; set; }
    }

    /// <summary>
    /// An example of a very basic web service.
    /// </summary>
    public class GreetService : IService<Greet>
    {
        public object Execute(Greet request)
        {
            return new GreetResponse { Result = "Hello " + request.Name };
        }
    }

    public static class Constants
    {
        public const string DefaultNamespaceV1 = "http://my/custom/namespace";
    }

    public class MyAppHost : AppHostBase
    {
        // Tell Service Stack the name of your application and where to find your web services.
        public MyAppHost()
            : base("My Web Services", typeof(GreetService).Assembly)
        {
        }

        public override void Configure(Container container)
        {
            SetConfig(new EndpointHostConfig { WsdlServiceNamespace = Constants.DefaultNamespaceV1 });

            // Register user-defined REST-ful URLs.
            Routes
                .Add<Greet>("/hello")
                .Add<Greet>("/hello/{Name}")
                .Add<Greet>("/hello/{Name*}");
        }
    }

    public class MvcApplication : HttpApplication
    {
        protected void Application_Start()
        {
            new MyAppHost().Init();
        }
    }
}

并将以下行添加到 AssemblyInfo.cs:

[assembly: ContractNamespace("http://my/custom/namespace", ClrNamespace = "TheGuest.Test")]

它将生成以下 WSDL:

<?xml version="1.0" encoding="utf-8"?>
<wsdl:definitions name="Soap12" 
    targetNamespace="http://my/custom/namespace" 
    xmlns:svc="http://my/custom/namespace" 
    xmlns:tns="http://my/custom/namespace" 

    xmlns:wsdl="http://schemas.xmlsoap.org/wsdl/" 
    xmlns:soap="http://schemas.xmlsoap.org/wsdl/soap/" 
    xmlns:soap12="http://schemas.xmlsoap.org/wsdl/soap12/" 
    xmlns:wsu="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd" 
    xmlns:soapenc="http://schemas.xmlsoap.org/soap/encoding/" 
    xmlns:wsam="http://www.w3.org/2007/05/addressing/metadata" 
    xmlns:wsa="http://schemas.xmlsoap.org/ws/2004/08/addressing" 
    xmlns:wsp="http://schemas.xmlsoap.org/ws/2004/09/policy" 
    xmlns:wsap="http://schemas.xmlsoap.org/ws/2004/08/addressing/policy" 
    xmlns:xsd="http://www.w3.org/2001/XMLSchema" 
    xmlns:msc="http://schemas.microsoft.com/ws/2005/12/wsdl/contract" 
    xmlns:wsaw="http://www.w3.org/2006/05/addressing/wsdl" 
    xmlns:wsa10="http://www.w3.org/2005/08/addressing" 
    xmlns:wsx="http://schemas.xmlsoap.org/ws/2004/09/mex">

    <wsdl:types>
        <xs:schema xmlns:tns="http://schemas.microsoft.com/2003/10/Serialization/" attributeFormDefault="qualified" elementFormDefault="qualified" targetNamespace="http://schemas.microsoft.com/2003/10/Serialization/" xmlns:xs="http://www.w3.org/2001/XMLSchema">
  <xs:element name="anyType" nillable="true" type="xs:anyType" />
  <xs:element name="anyURI" nillable="true" type="xs:anyURI" />
  <xs:element name="base64Binary" nillable="true" type="xs:base64Binary" />
  <xs:element name="boolean" nillable="true" type="xs:boolean" />
  <xs:element name="byte" nillable="true" type="xs:byte" />
  <xs:element name="dateTime" nillable="true" type="xs:dateTime" />
  <xs:element name="decimal" nillable="true" type="xs:decimal" />
  <xs:element name="double" nillable="true" type="xs:double" />
  <xs:element name="float" nillable="true" type="xs:float" />
  <xs:element name="int" nillable="true" type="xs:int" />
  <xs:element name="long" nillable="true" type="xs:long" />
  <xs:element name="QName" nillable="true" type="xs:QName" />
  <xs:element name="short" nillable="true" type="xs:short" />
  <xs:element name="string" nillable="true" type="xs:string" />
  <xs:element name="unsignedByte" nillable="true" type="xs:unsignedByte" />
  <xs:element name="unsignedInt" nillable="true" type="xs:unsignedInt" />
  <xs:element name="unsignedLong" nillable="true" type="xs:unsignedLong" />
  <xs:element name="unsignedShort" nillable="true" type="xs:unsignedShort" />
  <xs:element name="char" nillable="true" type="tns:char" />
  <xs:simpleType name="char">
    <xs:restriction base="xs:int" />
  </xs:simpleType>
  <xs:element name="duration" nillable="true" type="tns:duration" />
  <xs:simpleType name="duration">
    <xs:restriction base="xs:duration">
      <xs:pattern value="\-?P(\d*D)?(T(\d*H)?(\d*M)?(\d*(\.\d*)?S)?)?" />
      <xs:minInclusive value="-P10675199DT2H48M5.4775808S" />
      <xs:maxInclusive value="P10675199DT2H48M5.4775807S" />
    </xs:restriction>
  </xs:simpleType>
  <xs:element name="guid" nillable="true" type="tns:guid" />
  <xs:simpleType name="guid">
    <xs:restriction base="xs:string">
      <xs:pattern value="[\da-fA-F]{8}-[\da-fA-F]{4}-[\da-fA-F]{4}-[\da-fA-F]{4}-[\da-fA-F]{12}" />
    </xs:restriction>
  </xs:simpleType>
  <xs:attribute name="FactoryType" type="xs:QName" />
  <xs:attribute name="Id" type="xs:ID" />
  <xs:attribute name="Ref" type="xs:IDREF" />
</xs:schema>
<xs:schema xmlns:tns="http://my/custom/namespace" elementFormDefault="qualified" targetNamespace="http://my/custom/namespace" xmlns:xs="http://www.w3.org/2001/XMLSchema">
  <xs:complexType name="Greet">
    <xs:sequence>
      <xs:element minOccurs="0" name="Name" nillable="true" type="xs:string" />
    </xs:sequence>
  </xs:complexType>
  <xs:element name="Greet" nillable="true" type="tns:Greet" />
</xs:schema>
    </wsdl:types>



    <wsdl:message name="GreetIn">
        <wsdl:part name="parameters" element="tns:Greet" />
    </wsdl:message>

    <wsdl:portType name="ISyncReply">

    </wsdl:portType>

    <wsdl:portType name="IOneWay">
    <wsdl:operation name="Greet">
        <wsdl:input message="svc:GreetIn" />
    </wsdl:operation>
    </wsdl:portType>

    <wsdl:binding name="WSHttpBinding_ISyncReply" type="svc:ISyncReply">
        <soap12:binding transport="http://schemas.xmlsoap.org/soap/http" />

    </wsdl:binding>

    <wsdl:binding name="WSHttpBinding_IOneWay" type="svc:IOneWay">
        <soap12:binding transport="http://schemas.xmlsoap.org/soap/http" />
        <wsdl:operation name="Greet">
      <soap:operation soapAction="http://schemas.servicestack.net/types/Greet" style="document" />
      <wsdl:input>
        <soap:body use="literal" />
      </wsdl:input>
    </wsdl:operation>
    </wsdl:binding>

    <wsdl:service name="SyncReply">
        <wsdl:port name="WSHttpBinding_ISyncReply" binding="svc:WSHttpBinding_ISyncReply">
            <soap:address location="http://localhost:50472/test/soap12" />
        </wsdl:port>
    </wsdl:service>

    <wsdl:service name="AsyncOneWay">
        <wsdl:port name="WSHttpBinding_IOneWay" binding="svc:WSHttpBinding_IOneWay">
            <soap:address location="http://localhost:50472/test/soap12" />
        </wsdl:port>
    </wsdl:service>

</wsdl:definitions>

当我通过 Visual Studio 2010 添加此服务时,我得到了 2 个客户端。一个叫做“SyncReplyClient”,它没有我可以调用的方法,另一个叫做“OneWayClient”,带有一个“Greet”方法。但顾名思义,我想使用 SyncReplyClient,因为我需要响应。

我如何实现这一目标?

附带说明一下,OneWayClient 会抛出带有以下消息的 ProtocolException:“单向操作返回了一条带有 Action='' 的非空消息。” 因为我不想使用 OneWayClient,所以这并没有给我带来太多困扰,但这仍然很奇怪。

4

3 回答 3

5

在创建您希望通过 SOAP 使用的服务时,请确保您阅读了有关SOAP 限制的信息。即,您需要保留一个 XSD/WSDL 命名空间。例如,您可以使用以下命令更改 AppConfig 中的默认 WSDL 命名空间:

SetConfig(new EndpointHostConfig {
    WsdlServiceNamespace = "http://my.new.namespace.com/types",
});

这设置了在生成的 WSDL 页面上使用的 WSDL/XSD 命名空间。您还需要[DataContract]通过为每个 DataContract 指定一个名称空间来将此自定义 XSD 名称空间与您的 DTO 匹配,您可以通过在每个 DataContract 上手动指定

[DataContract(Namespace="http://my.new.namespace.com/types")]

或者您可以使用指定

 [assembly: ContractNamespace("http://my/custom/namespace", 
            ClrNamespace = "TheGuest.Test")] 

将其设置在共享 C# 命名空间下的多个 DTO 上。

最近也发生了一些变化,我们添加了新 API并添加了不同的属性以允许您注释您的服务(将出现在 /metadata 和 Api Docs/Swagger 页面上)。考虑到这些变化,创建服务的新方法是:

[DataContract]
[Api("A sample web service.")]
public class Greet
{
    [DataMember]
    [ApiMember("The name of the person you wish to greet")]
    public string Name { get; set; }
}

[DataContract]
public class GreetResponse
{
    [DataMember]
    public string Result { get; set; }
}

public class GreetService : Service
{
    public GreetResponse Any(Greet request)
    {
        return new GreetResponse { Result = "Hello " + request.Name };
    }
}

告诉 ServiceStack 服务响应类型是什么

为了让 ServiceStack 确定您的服务的响应类型是什么,您需要提供以下任何提示:

使用强类型返回类型

您的服务可以返回一个object或现在一个ResponseDto类型,例如:

public class GreetService : Service
{
    //1. Using Object
    public object Any(Greet request)
    {
        return new GreetResponse { Result = "Hello " + request.Name };
    }

    //2. Above service with a strong response type
    public GreetResponse Any(Greet request)
    {
        return new GreetResponse { Result = "Hello " + request.Name };
    }
}

如果您使用选项 2) ServiceStack 将假定一个GreetResponse类型。

使用 IReturn 标记接口

[DataContract]
public class Greet : IReturn<GreetResponse> { ... }

使用 Marker 接口的另一个优点是它提供了更简洁的客户端 API,例如:

GreetResponse response = client.Send(new Greet { Name = "World!" });

如果您没有 Marker 接口,则客户端 API 将是:

GreetResponse response = client.Send<GreetResponse>(new Greet { Name = "World!" });

使用 typeof(RequestDto).Name + 'Response' 命名约定

如果您的服务具有object响应类型且没有标记接口,那么您可以使用名称{RequestDto}Response命名约定来告诉 ServiceStack 响应类型是什么。

注意:要使 ServiceStack 能够找到响应类型,它需要与请求 DTO 位于相同的命名空间中。此外,每个请求和响应 DTO 都应该被唯一命名,这让您可以仅使用请求 DTO 的名称而不是完整的命名空间来调用 ServiceStack 服务。

于 2012-12-24T00:21:37.873 回答
2

您是否有理由引用服务并生成代理类?(很抱歉要求澄清,但我没有足够的声望对这个问题发表评论。)

如果这是您的第一个 ServiceStack 服务,那么您应该了解它是如何与 WCF 不同的范例。这是一个快速的总结

SS 的伟大之处在于您不需要生成代理,您可以专注于传输数据而不是执行远程操作。只需有一个指定 DTO 的 DLL,并且服务项目和客户端项目都引用该 DLL。

这使您能够使用内置的服务客户端(JSON/SOAP/等)与服务进行通信。了解这些差异需要一些时间,但是一旦您了解了 ServiceStack 的不同之处(尤其是关于从 Javascript 或其他非 C# 客户端调用服务方面)并且您需要在不重新生成代理的情况下重构您的服务层然后您会看到为什么它更好。

抱歉,如果您特别需要生成客户端代理并且这不能回答问题;这个链接显示了使用通用客户端是多么容易,我强烈建议采用这种方法,除非有一些特殊的原因来生成 SOAP。

于 2012-10-18T05:54:04.993 回答
1

我找到了!

我检查了 ServiceStack 的示例,它们可以工作,但我的程序没有。

不同之处在于操作 (SayHello) 和响应 (SayHelloResponse) 不在同一个命名空间(My.Application.Operations 和 MyApplication.Responses)中。

当我将它们放在同一个命名空间中时,它就可以工作了,我的服务不再是“OneWay”而是“SyncReply”。

于 2012-12-23T22:41:20.583 回答