Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Visual Studio referencing a ServiceStack SOAP method

I have a simple API setup using ServiceStack. I use the following code to get it running:

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

And adding the following line to the AssemblyInfo.cs:

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

It will generate the following 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>

When I add this service via Visual Studio 2010, I get 2 clients. One is called "SyncReplyClient" which has no methods I can call and the other is called "OneWayClient" with a "Greet" method. But as the names imply, I would like to use the SyncReplyClient since I need the response.

How do I achieve this?

On a side note, the OneWayClient throws an ProtocolException with the following message: "The one-way operation returned a non-null message with Action=''." which does not bother me as much since I don't want to use the OneWayClient but it's strange none the less.

like image 582
TheGuest Avatar asked Feb 19 '23 19:02

TheGuest


1 Answers

Make sure you read about SOAP Limitations when creating Services that you want to consume by SOAP. i.e. You need to keep a single XSD/WSDL namespace. E.g. You can change the default WSDL Namespace in your AppConfig with:

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

This sets what WSDL/XSD namespace gets used on the generated WSDL page. You also need to match this custom XSD namespace with your [DataContract] DTOs by specifying a namespace for each DataContract which you can do by either specifying manually on each

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

or you can use specify the

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

to set it on a number of DTO's under a shared C# namespace.

Also a few things have changed recently, we've added the New API and added different attributes to allow you annotate your services (that will appear on the /metadata and Api Docs/Swagger pages). Taking account of these changes the new way to create your service is:

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

Telling ServiceStack what the Services Response Type is

In order for ServiceStack to determine what the Response Type of your service is, you need to provide any of the below hints:

Using a strong type return type

Your services can either return an object or now a ResponseDto type, e.g:

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

If you use option 2) ServiceStack will assume a GreetResponse type.

Use the IReturn marker interface

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

Another advantage of using a Marker interface is that it provides a more succinct client API, e.g:

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

If you didn't have the Marker interface the client API would've been:

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

Use a typeof(RequestDto).Name + 'Response' naming convention

If your services have an object response type and no marker interface than you can use the name {RequestDto}Response naming convention to tell ServiceStack what the Response type is.

Note: For ServiceStack to be able to find the Response type it needs to be in the same namespace as the Request DTO. Also every Request and Response DTO should be uniquely named, this is what lets you call a ServiceStack service with just the Name of the Request DTO and not the full namespace.

like image 86
mythz Avatar answered Mar 04 '23 11:03

mythz