I would like to replace a WCF Web-service by a new ServiceStack service. WCF service uses basicHttpBinding and it is invoked by a legacy client via SOAP using HTTP POST. The problem I faced to is that the legacy client uses [XmlSerializerFormat] instead of [DataContract], and it can not be changed. I inspected the SOAP envelopes produced by the client and shown by metadata page of my new ServiceStack service and haven't discovered any crucial mismatches in requests. They look closely the same except of responses. Service Stack's response uses a custom namespace.
Below is the WCF service's API, which works well with the legacy client. It has quite simple API: only two methods.
[ServiceContract(Namespace = WebConstants.SoapNamespace)]
[XmlSerializerFormat]
public interface IMicexNewsWcf
{
[OperationContract(Action = WebConstants.SoapNamespace + "PostNews")]
[FaultContract(typeof(ExceptionDetail))]
[PrincipalPermission(SecurityAction.Demand, Role = WebConstants.UserRole)]
string PostNews (MarketNewsRaw[] st, string rynok);
[OperationContract(Action = WebConstants.SoapNamespace + "SendMarkerUsingDB")]
[FaultContract(typeof(ExceptionDetail))]
[PrincipalPermission(SecurityAction.Demand, Role = WebConstants.UserRole)]
void SendMarkerUsingDB (string id, string dt, string status, string problem);
}
[XmlSerializerFormat]
public sealed class MarketNewsRaw
{
[DataMember(Order = 1)]
public string SysTime {get; set;}
[DataMember(Order = 2)]
public string Urgency {get; set;}
[DataMember(Order = 3)]
public string FromUser {get; set;}
[DataMember(Order = 4)]
public string MsgTime {get; set;}
[DataMember(Order = 5)]
public string MsgText {get; set;}
}
Service implementation:
[ServiceBehavior(ConcurrencyMode = ConcurrencyMode.Multiple, InstanceContextMode = InstanceContextMode.Single)]
public sealed class MicexNewsWcf: WcfServiceBase, IMicexNewsWcf
{
public const string SrvName = "NewsListener";
readonly IMicexNewsListenerService _listenerSrv;
readonly IAbstractLogger _logger;
public MicexNewsWcf (IAbstractLoggerFactory loggerFac, IMicexNewsListenerService listenerSrv): base(loggerFac.GetLogger(SrvName))
{
_listenerSrv = listenerSrv;
_logger = loggerFac.GetLogger(SrvName);
}
public string PostNews (MarketNewsRaw[] st, string rynok)
{
return "Ok";
}
public void SendMarkerUsingDB (string id, string dt, string status, string problem)
{
}
}
Example of SOAP XML sent by a legacy client:
<SOAP-ENV:Envelope xmlns:SOAPSDK1="http://www.w3.org/2001/XMLSchema" xmlns:SOAPSDK2="http://www.w3.org/2001/XMLSchema-instance" xmlns:SOAPSDK3="http://schemas.xmlsoap.org/soap/encoding/" xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/">
<SOAP-ENV:Body>
<PostNews xmlns="http://xxx/">
<st>
<MarketNewsRaw>
<SysTime>17.10.2013 10:35:38</SysTime>
<Urgency>U</Urgency>
<FromUser>MZ000020069A</FromUser>
<MsgTime>10:35:38</MsgTime>
<MsgText>Some text</MsgText>
</MarketNewsRaw>
<MarketNewsRaw>
<SysTime>17.10.2013 11:21:26</SysTime>
<Urgency>U</Urgency>
<FromUser>MM0000101281</FromUser>
<MsgTime>11:21:27</MsgTime>
<MsgText>XXX 2</MsgText>
<MarketNewsRaw>
</st>
<rynok>4</rynok>
</PostNews>
</SOAP-ENV:Body>
</SOAP-ENV:Envelope>
Exact SOAP which is accepted by my WCF service with basicHttpBinding.
To replace WCF service I implemented ServiceStack service, shown below.
[XmlSerializerFormat]
[Route("/MicexNews", "POST")]
public sealed class PostNews: IReturn<PostNewsResponse>
{
[DataMember(Order = 1)]
public MarketNewsRaw[] st;
[DataMember(Order = 2)]
public string rynok;
public override string ToString ()
{
StringBuilder bld = new StringBuilder();
if (st != null)
foreach (var s in st)
{
if (bld.Length > 0)
bld.Append(", ");
bld.AppendFormat("<{0}>", s.ToString());
}
return string.Format("st = [{0}], rynok = {1}", bld.ToString(), rynok);
}
}
[XmlSerializerFormat]
public sealed class PostNewsResponse: IHasResponseStatus
{
public PostNewsResponse ()
{
ResponseStatus = new ResponseStatus();
}
[DataMember(Order = 1)]
public ResponseStatus ResponseStatus { get; set; }
[DataMember(Order = 2)]
public string Result {get; set; }
}
[XmlSerializerFormat]
[Route("/MicexNews", "POST")]
public sealed class SendMarkerUsingDB: IReturn<SendMarkerUsingDBResponse>
{
[DataMember(Order = 1)]
public string id {get; set;}
[DataMember(Order = 2)]
public string dt {get; set;}
[DataMember(Order = 3)]
public string status {get; set;}
[DataMember(Order = 4)]
public string problem {get; set;}
public override string ToString ()
{
return string.Format("id = {0}, dt = {1}, status = {2}, problem = {3}", id, dt, status, problem);
}
}
[XmlSerializerFormat]
public sealed class SendMarkerUsingDBResponse: IHasResponseStatus
{
[DataMember(Order = 1)]
public ResponseStatus ResponseStatus { get; set; }
}
public sealed class MicexNewsWebService: Service, IPost<PostNews>, IPost<SendMarkerUsingDB>
{
public const string SrvName = "NewsListener";
readonly IMicexNewsListenerService _listenerSrv;
readonly IAbstractLogger _logger;
public MicexNewsWebService (IAbstractLoggerFactory loggerFac, IMicexNewsListenerService listenerSrv)
{
_listenerSrv = listenerSrv;
_logger = loggerFac.GetLogger(SrvName);
}
public object Post (PostNews req)
{
PostNewsResponse stat = new PostNewsResponse();
stat.Result = "Ok";
return stat;
}
public object Post (SendMarkerUsingDB request)
{
SendMarkerUsingDBResponse stat = new SendMarkerUsingDBResponse();
return stat;
}
}
Metadata page shows the SOAP example:
<soap:Envelope xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
<soap:Body>
<PostNews xmlns:i="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://xxx/">
<rynok>String</rynok>
<st>
<MarketNewsRaw>
<FromUser>String</FromUser>
<MsgText>String</MsgText>
<MsgTime>String</MsgTime>
<SysTime>String</SysTime>
<Urgency>String</Urgency>
</MarketNewsRaw>
</st>
</PostNews>
</soap:Body>
</soap:Envelope>
The response:
<soap:Envelope xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
<soap:Body>
<PostNewsResponse xmlns:i="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://xxx/">
<ResponseStatus xmlns:d2p1="http://schemas.servicestack.net/types">
<d2p1:ErrorCode>String</d2p1:ErrorCode>
<d2p1:Message>String</d2p1:Message>
<d2p1:StackTrace>String</d2p1:StackTrace>
<d2p1:Errors>
<d2p1:ResponseError>
<d2p1:ErrorCode>String</d2p1:ErrorCode>
<d2p1:FieldName>String</d2p1:FieldName>
<d2p1:Message>String</d2p1:Message>
</d2p1:ResponseError>
</d2p1:Errors>
</ResponseStatus>
<Result>String</Result>
</PostNewsResponse>
</soap:Body>
</soap:Envelope>
So, I am able to call ServiceStack service by means of a test ServiceStack Json client, for example, but the call originating from my legacy client is failed.
How could I adjust ServiceStack to understand old RPC SOAP, as well as WCF does, along with [XmlSerializerFormat] attribute specified on interface?
Legacy client reports 400 Bad Request and my ServiceStack service has not been even get called. I mean, method public object Post (PostNews req) has not been called.
Related
Studying WCF RESTful, host in a Console,
my steps:
create sample models
create contract of service
create service
host this service in a console.
run this host, looks ok.
create a winform, use service address via json post to service host.
i hope it would work, but return http 400.
I tried on WCF(not REST) Console-hosted, WebAPI, steps all ok.
finally, stackoverflow.com
please help
Models
[Serializable]
public abstract class Building
{
public Manufacturer Manufacturer { get; set; }
}
[Serializable]
public class Manufacturer
{
public string Name { get; set; }
public string Telephone { get; set; }
}
[Serializable]
public class Furniture : Building
{
public string Name { get; set; }
}
[Serializable]
public class Reception
{
public int Floor { get; set; }
public int Number { get; set; }
}
[Serializable]
public class Room : Building
{
public string Number { get; set; }
public List<Furniture> Furnitures { get; set; }
}
[Serializable]
public class Hotel : Building
{
public Guid Guid { get; set; }
public List<Reception> Receptions { get; set; }
public List<Room> Rooms { get; set; }
public string Name { get; set; }
public string Address { get; set; }
}
Contract
[ServiceContract]
public interface IHotel
{
// Create objct Hotel
[OperationContract]
[WebInvoke(UriTemplate = "", Method = "POST", RequestFormat= WebMessageFormat.Json, ResponseFormat=WebMessageFormat.Json)]
bool Create(Hotel hotel);
}
Service
public class HotelService : I Hotel
{
public bool Build(Models.Hotel hotel)
{
if (hotel == null)
return false;
// codes here is object hotel(EF) creation, test OK
return true;
}
}
Host(Console)
WebServiceHost serviceHost = new WebServiceHost(typeof(Demo.Services.HotelService), new Uri("http://192.168.1.101/HotelService"));
ServiceEndpoint endpoint = serviceHost.AddServiceEndpoint(typeof(Demo.Contracts.IHotel), new WebHttpBinding(), "");
ServiceDebugBehavior sdb = serviceHost.Description.Behaviors.Find<ServiceDebugBehavior>();
sdb.HttpHelpPageEnabled = false;
Console.WriteLine("Starting Service...");
// start service
serviceHost.Open();
Console.WriteLine("Started, press RETURN to exit.");
Console.ReadLine();
serviceHost.Close();
Client(Winform)
Caller
public bool BuildHotel(string json)
{
WebRequest request = HttpWebRequest.Create("http://192.168.1.101/HotelService");
request.ContentType = "application/json";
byte[] data = Encoding.UTF8.GetBytes(json);
request.ContentLength = data.Length;
Stream requestStream = request.GetRequestStream();
requestStream.Write(data, 0, data.Length);
requestStream.Close();
WebResponse response = request.GetResponse(); // 400 throwed here
Stream responseStream = response.GetResponseStream();
StreamReader responseStreamReader = new StreamReader(responseStream);
string result = responseStreamReader.ReadToEnd();
return true;
}
Json String for 'bool BuildHotel(string)' upon
{
"Guid":"ea59c011-d656-4870-b29b-30a44e668560",
"Receptions":[
{"Floor":1,"Number":1},
{"Floor":2,"Number":2}
],
"Rooms":[
{
"Number":"c",
"Furnitures":[
{"Name":"1","Manufacturer":{"Name":"1","Telephone":"1"}},
{"Name":"2","Manufacturer":{"Name":"2","Telephone":"2"}}
],
"Manufacturer":{"Name":"c","Telephone":"c"}
}
],
"Name":"x",
"Address":"x",
"Manufacturer":{"Name":"x","Telephone":"x"}
}
it expects such kind of JSON:
{"k_BackingField":{"k_BackingField":"4","k_BackingField":"4"},"k_BackingField":"x","k_BackingField":"ea59c011-d656-4870-b29b-30a44e668560","k_BackingField":"x","k_BackingField":[{"k_BackingField":1,"k_BackingField":1},{"k_BackingField":2,"k_BackingField":2}],"k_BackingField":[{"k_BackingField":{"k_BackingField":"3","k_BackingField":"3"},"k_BackingField":[{"k_BackingField":{"k_BackingField":"1","k_BackingField":"1"},"k_BackingField":"1"},{"k_BackingField":{"k_BackingField":"2","k_BackingField":"2"},"k_BackingField":"2"}],"k__BackingField":null}]}
To change it you can mark all your data contracts with DataContract and DataMember attribute:
[DataContract]
public abstract class Building
{
[DataMember]
public Manufacturer Manufacturer { get; set; }
}
In this case it will understand json as you have given us in the question and will process it successfully.
I have a XML structure like this:
<Message>
<Messagehead>
<OSType>Android</OSType>
<RouteDest>SiteServerName</RouteDest>
<ActionType>Enroll</ActionType>
</Messagehead>
<MessageBody>
<Raw>
<![CDATA[OrienginalMessageContent]]>
</Raw>
</MessageBody>
</Message>
and I want upload this XML to WCF 4.0 my rest service:
public string Enroll(Message instance)
{
// TODO: Add the new instance of SampleItem to the collection
return "success";
}
the Message is a DataContract type, I setup it like below:
[DataContract(Namespace = "")]
public class Message
{
[DataMember]
public MessageHead MessageHead { get; set; }
[DataMember]
public MessageBody MessageBody { get; set; }
}
public class MessageHead
{
public OSType OSType { get; set; }
public string RouteDest { get; set; }
public Action Action { get; set; }
}
public class MessageBody
{
public string RawRequestContent { get; set; }
}
but when I get the Message instance from the server side, all the property is null, except the OSType, can anybody tell me why? How could I solve this problem?
Besides being a really bad name for a class (since it's already used in the WCF runtime), your Message class also has some flaws:
<Message>
<Messagehead>
....
</Messagehead>
Your <Messagehead> has a lower-case h in the middle - yet your class defines it to be upper case:
[DataContract(Namespace = "")]
public class Message
{
[DataMember]
public MessageHead MessageHead { get; set; }
This will not work - case is important and relevant in a WCF message! If your XML has a lower-case h, so must your DataContract class!
Your XML also requires a <Raw> tag inside your <MessageBody>
<MessageBody>
<Raw>
<![CDATA[OriginalMessageContent]]>
</Raw>
</MessageBody>
yet your data contract doesn't respect that:
public class MessageBody
{
public string RawRequestContent { get; set; }
}
Again - those don't line up! Names are important - and they must match between your XML representation of the message, and the C# class representing that message.....
I'm creating a wcf wrapper service that returns xml from another web service.
What I'm noticing is that not all the xml from the source is being serialized to my object.
Here's the source xml:
<?xml version="1.0" standalone="yes"?>
<methodResponse>
<requestId>234843080</requestId>
<errorCode>0</errorCode>
<errorText></errorText>
<processingTime>00:00:00.234</processingTime>
<results>
<resultCode>0</resultCode>
<resultText></resultText>
<resultCount>1</resultCount>
<totalResultCount>1</totalResultCount>
<result>
<contactId>123</contactId>
<sourceFolderId>3443</sourceFolderId>
<contactState>4</contactState>
<contactStateSortOrder>3</contactStateSortOrder>
<contactType>person</contactType>
<displayName>Bond, James</displayName>
<office>unknown</office>
<department></department>
<changeDate></changeDate>
<firstName>James</firstName>
<middleName></middleName>
<lastName>Bond</lastName>
<jobTitle>Spy</jobTitle>
<ape>
<address type='Street' typeId='1' relationship='Business' relationshipId='1'>
<addressLine></addressLine>
<city></city>
<state></state>
<postalCode></postalCode>
</address>
<phone type='Phone' typeId='1' relationship='Business' relationshipId='1'>
<phoneNumber></phoneNumber>
<description/>
<formattedPhone></formattedPhone>
</phone>
</ape>
</result>
</results>
</methodResponse>
The wcf wrapper service:
[DataContract(Name="methodResponse", Namespace = "")]
public partial class methodResponse {
[DataMember]
public int errorCode {
get;
set;
}
[DataMember]
public string errorText {
get;
set;
}
[DataMember]
public string requestId
{
get;
set;
}
[DataMember]
public methodResponseResults[] results
{
get;
set;
}
}
[DataContract(Namespace = "")]
public partial class methodResponseResults
{
[DataMember]
public string resultCode {
get;
set;
}
[DataMember]
public string resultText {
get;
set;
}
[DataMember]
public string resultCount {
get;
set;
}
[DataMember]
public string totalResultCount {
get;
set;
}
[DataMember]
public methodResponseResultsResult[] result {
get;
set;
}
}
[DataContract(Namespace="")]
public partial class methodResponseResultsResult {
public string contactId
{
get;
set;
}
public string displayName
{
get;
set;
}
}
[ServiceContract]
[AspNetCompatibilityRequirements(RequirementsMode = AspNetCompatibilityRequirementsMode.Allowed)]
[ServiceBehavior(InstanceContextMode = InstanceContextMode.Single)]
public class WcfWrapperService
{
public Response GetXml()
{
string uri = ""; // uri to source web service
var dataContractSerializer = new DataContractSerializer(typeof(methodResponse));
using (XmlReader reader = XmlReader.Create(uri))
{
var result = (methodResponse)dataContractSerializer.ReadObject(reader);
//result object looks like:
//result.errorCode = 0;
//result.errorText = null;
//result.requestId = 234843080;
//result.results = methodResponseResults[0];
}
// return Response object
}
}
The errorCode, errorText, requestId values are there but no results.
The first thing that jumps out is that you are trying to de/serialize a contract which contains attributes. DataContractSerializer has a few restrictions, one of which is that it can only serialize/deserialize elements. In order to properly handle this Schema, you will need to use an XmlSerializer.
Your not setting any values for the Order property on your DataMember attributes. This means that the elements will be processed in alphabetical order which is most likely NOT what you want. You must explicitly set Order to ensure it matches the schema of the documents you will process. You may also need to set IsRequired = false if there are any elements that may or not be in the instance document.
I have a type MyParameter that i pass as a parameter to a wcf service
[Serializable]
public class MyParameter : IXmlSerializable
{
public string Name { get; set; }
public string Value { get; set; }
public string Mytype { get; set; }
#region IXmlSerializable Members
public System.Xml.Schema.XmlSchema GetSchema()
{
return null;
}
public void ReadXml(System.Xml.XmlReader reader)
{
XElement e = XElement.Parse(reader.ReadOuterXml());
IEnumerable<XElement> i = e.Elements();
List<XElement> l = new List<XElement>(i);
Name = l[0].Name.ToString();
Value = l[0].Value.ToString();
Mytype = l[0].Attribute("type").Value.ToString();
}
public void WriteXml(System.Xml.XmlWriter writer)
{
writer.WriteStartElement(Name);
writer.WriteAttributeString("xsi:type", Mytype);
writer.WriteValue(Value);
writer.WriteEndElement();
}
#endregion
}
The service contract looks like this:
[ServiceContract]
public interface IOperation
{
[OperationContract]
void Operation(List<Data> list);
}
Where data defines a data contract
[DataContract]
public class Data
{
public string Name { get; set; }
public List<MyParameter> Parameters{ get; set; }
}
When I run the service and test it
I get rhe exception in readXml of MyParameter
"the prefix xsi is not defined"
xsi should define the namespace "http://w3.org/2001/xmlschema-instance"
How do I fix the problem
I am very new to this so a sample code will be very very very helpful
thanks
Add:
writer.WriteAttributeString("xmlns","xsi", null,#"http://w3.org/2001/xmlschema-instance");
I am writing a sample application using wcf rest for authentication. Here is the snapshot of the code:
service Interface:
[ServiceContract]
public interface IAuthenticate
{
[OperationContract]
[WebInvoke(BodyStyle=WebMessageBodyStyle.Bare,
Method = "POST", UriTemplate = "/VUser",RequestFormat= WebMessageFormat.Xml ), ]
string CreateUser(VUser user);
}
Datacontract class:
[DataContract]
public class VUser
{
public VUser()
{
}
[DataMember]
public string NickName { get; set; }
[DataMember]
public string lName { get; set; }
[DataMember]
public string fName { get; set; }
[DataMember]
public string Email { get; set; }
[DataMember]
public string PhoneNumber { get; set; }
[DataMember]
public string Password { get; set; }
[DataMember]
public string Gender { get; set; }
[DataMember]
public int CountryCode { get; set; }
}
Service class:
public class Authenticate : IAuthenticate
{
#region IAuthenticate members
public string CreateUser(Vuser user)
{
//processing xml for response
}
#endregion IAuthenticate
}
client code:
Uri baseAddress = new Uri("http://localhost:8000");
using (WebServiceHost host = new WebServiceHost(typeof(Authenticate), baseAddress))
{
host.Open();
Console.WriteLine("Press any key to terminate");
Console.ReadLine();
host.Close();
}
Now I am using fiddler to send the request after host.open() and send the the request has shown:
post http://localhost:8000/Vuser/
User-Agent: Fiddler
Host: localhost:8000
content-length: 233
content-type: text/xml
and in request body :
sandy
r
sunil
sunil.r
919900101948
winter
male
01
but it is returning me HTTP/1.1 400 Bad Request. My question is am I passing the vuser class correctly to the create user method or is there any other way to send the vuser.
Please help me.
It could be a problem with serialization.
Serialization uses the default consrtuctor, without parameters.
In C# the compiler will automatically create a default constructor, except if you create a constructor with a parameter.
The Authenticate class is missing a default constructor, you will therefore have probelms sending it over WCF.
Kindly specify The Datacontract Namespace in DataContract Class
[DataContract(Namespace = "http://xxx.xxx.xxx/Service.svc")]
and follow same in Xml file
The Namespace in both client and server should match. Try to add namespace name as
[DataContract(Namespace = "http://sample.com")]
public class VUser
in the server contract. And then make sure the xml string has the xmlns value with the same namespace
"<VUser xmlns=\"http://sample.com" xmlns:i=\"http://www.w3.org/2001/XMLSchema-instance\">...</VUser>"