I've WCF service operation defined in a simple way like
[ServiceContract(Namespace = "http://contoso.com")]
interface ICalculator
{
[OperationContract]
AddResp Add(AddRqst addRequestDummyName);
}
where AddRqst is defined as
[DataContract]
class AddRqst
{
[DataMember]
public decimal A { get; set; }
[DataMember]
public decimal B { get; set; }
}
and the problem I see is that the way WCF serializes and the way it expects requests are wrapped by the parameter name like so (this is from memory)
<Add>
<addRequestDummyName>
<A>1</A>
<B>1.5</B>
</addRequestDummyName>
</Add>
I can't distribute a schema for this file because WCF likes to add and expects that extra wrapper for the parameters. It should look like I designed to be, like so
<Add>
<A>1</A>
<B>1.5</B>
</Add>
I'm aware of MessageContract and I've tested that by changing AddRqst (and also AddResp) to the following removes the wrapping because by using messagecontracts you assume just 1 contract.
[DataContract]
[MessageContract]
class AddRqst
{
[DataMember]
[MessageBodyMember]
public decimal A { get; set; }
[DataMember]
[MessageBodyMember]
public decimal B { get; set; }
}
My question is the following. Is there way to do this just with datacontracts? All my operations have 1 parameter. If not, is the way I modified AddRqst the best way to do this?
Related
I am attempting to implement a portion of the Ally Accounts API in C#. I've run into a problem with a particular endpoint.
I want to get a specific account's balance:
[XmlRoot("accountbalance")]
public class SummaryAccountBalance : Balance
{
[XmlElement("buyingpower")]
public BuyingPower BuyingPower { get; set; }
[XmlElement("fedcall")]
public decimal FedCall { get; set; }
[XmlElement("housecall")]
public decimal HouseCall { get; set; }
[XmlElement("money")]
public AccountMoney Money { get; set; }
[XmlElement("securities")]
public Securities Securities { get; set; }
}
Where Balance is a simple abstract class:
public abstract class Balance
{
[XmlElement("account")]
public int Account { get; set; }
[XmlElement("accountvalue")]
public decimal AccountValue { get; set; }
}
For other endpoints this works just fine and I am able to get summary account balance information using this structure, for example in this endpoint.
But the /accounts/xxxxxxx/balances.xml doesn't want to work for me. Given this file:
<?xml version="1.0" encoding="UTF-8"?>
<response id="77cf30da:12df25c7074:-7ea6">
<accountbalance>
<account>12345678</account>
<accountvalue>67119.41</accountvalue>
<buyingpower>
<cashavailableforwithdrawal>66177.48000000001</cashavailableforwithdrawal>
<daytrading>264709.84</daytrading>
<equitypercentage>100</equitypercentage>
<options>66177.48000000001</options>
<soddaytrading>264709.84</soddaytrading>
<sodoptions>66177.48000000001</sodoptions>
<sodstock>132354.96000000002</sodstock>
<stock>132354.96000000002</stock>
</buyingpower>
<fedcall>0.0</fedcall>
<housecall>0.0</housecall>
<money>
<accruedinterest>0.0</accruedinterest>
<cash>66134.67</cash>
<cashavailable>0.0</cashavailable>
<marginbalance>0.0</marginbalance>
<mmf>0.02</mmf>
<total>66134.69</total>
<uncleareddeposits>0.0</uncleareddeposits>
<unsettledfunds>0.0</unsettledfunds>
<yield>0.0</yield>
</money>
<securities>
<longoptions>0.0</longoptions>
<longstocks>57.39</longstocks>
<options>0.0</options>
<shortoptions>0.0</shortoptions>
<shortstocks>0.0</shortstocks>
<stocks>57.39</stocks>
<total>984.72</total>
</securities>
</accountbalance>
</response>
The following code detects it is indeed a SummaryAccountBalance but fails to populate the fields:
var serializer = new XmlSerializer(typeof(SummaryAccountBalance), new XmlRootAttribute("response"));
return (SummaryAccountBalance)serializer.Deserialize(summaryAccountBalance);
where summaryAccountBalance is the byte stream version of the file above.
I'm not sure where I have gone wrong here and I'm pulling my hair out trying to solve it. Even stepping through the code line by line there doesn't seem to be any problems at all until this deserialization step. More to the point, this exact object is used in other endpoints and those endpoint implementations have no problem deserializing it.
What am I missing to make this work?
What am I missing to make this work?
What you are missing is that <response> is not your SummaryAccountBalance. It's the nested <accountbalance> element.
You need to declare one more class to describe the whole XML:
[XmlRoot("response")]
public class SummaryAccountBalanceResponse
{
[XmlAttribute("id")]
public string Id { get; set; }
[XmlElement("accountbalance")]
public SummaryAccountBalance Balance { get; set; }
}
and deserialize into this class:
var serializer = new XmlSerializer(typeof(SummaryAccountBalanceResponse));
return ((SummaryAccountBalanceResponse)serializer.Deserialize(summaryAccountBalance)).Balance;
I need to write WCF service wich will take .xml file deserialize it into my custom calss and then do some operations on that data. I have written test version of this application which use console but not WCF. Here is how my code looks like:
[Serializable]
public class ErrorMsgElement
{
[XmlElement("Sender")]
public string SenderOfMessage{ get; set; }
[XmlElement()]
public Int64 UserID { get; set; }
[XmlElement()]
public Int64 SerialNumber { get; set; }
[XmlElement("DateTime")]
public DateTime DateAndTimeOfMessage { get; set; }
}
[Serializable]
[XmlRoot("Root")]
public class ErrorMessage
{
[XmlElement("Header")]
public string HeaderOfFile { get; set; }
[XmlElement("ErrorMsg")]
public ErrorMsgElement msgElent { get; set; }
}
And Main code
ErrorMessage myErrorMsg = new ErrorMessage();
//pathToFile is original path
string path = #pathToFile;
XmlSerializer myXmlSerializer = new XmlSerializer(typeof(ErrorMessage));
using (StreamReader myStrReader = new StreamReader(path))
{
myErrorMsg = (ErrorMessage)myXmlSerializer.Deserialize(myStrReader);
}
//some operations on myErrorMsg such as writing to database
It works fine in console application. Now I need to write same logic in WCF service. But I don't know what should I use - [Serializable], [DataContact], [MessageContract] or someting else?. Consider that I have some [XmlArray] atributes in my custom classes as well. And if I add [DataContract] atribute to my ErrorMessage class do I have to add same attribute to ErrorMsgElement as following?
[DataContract]
[Serializable]
public class ErrorMsgElement
{
...
}
[DataContract]
[Serializable]
[XmlRoot("Root")]
public class ErrorMessage
{
...
[DataMember]
[XmlElement("ErrorMsg")]
public ErrorMsgElement msgElent { get; set; }
}
All answers are well appreciated. Thanks in advance.
in wcf [DataContact] and DataContractSerializer are recommended for what do you want to do. But you can also use XmlSerializer as well it's really only your choice(using first or second don't making diffrent for performance of your application).
Edit:
When you adding DataContract attribute to your class than you don't have to add Serializable attribute too the same class DataContract is equal to Serializable
check this for answer about nested DataContracts
My client has 10 tables that it needs to load via an internal WCF to a server. Since all this is internal, I can write both client and server using whatever technique i want.
On the Client, I thought to use LINQ to load data from the tables to a List, List and so on...
On the Server, I thought to have a [DataContract] as follow:
[DataContract]
[KnownType(typeof(Table1))]
[KnownType(typeof(Table2))]
[KnownType(typeof(Table3))]
public class GenericType<T>
{
[DataMember]
public List<T> Data { get; set; }
}
and then add the classes that will represent the matching Tables on the Client.
[DataContract]
public class Table1
{
[DataMember]
public int UserID { get; set; }
[DataMember]
public string FullName { get; set; }
}
[DataContract]
public class Table2
{
[DataMember]
public int UserID { get; set; }
[DataMember]
public string Address1 { get; set; }
}
[DataContract]
public class Table3
{
[DataMember]
public string Name { get; set; }
[DataMember]
public string Description { get; set; }
}
When I create the client reference, i'm NOT getting all the classes declared on the server and it seems that ONLY the 1st [KnownType] specified on the [DataContract] becomes visible to the Client.
I was under the impression that Generics was meant to allow multiple types but am I right to think that WCF can only handle one [KnownType] x class ??
And if so, my only way to code this would be to copy and paste 10 times the GenericType class and on each copy, change the [KnownType] ??
Cause if that's the only solution, then what are the real benefits to use Generic instead of straight defined List, List for my params ??
Any thought will help clarify my mind here
The problem happens because unless ONE of the WCF methods uses any of the CLASSES declared as [DataContract] ...it seems that WCF does NOT brings those classes to the Client.
Is this the expected case?
You could try attributing your interface method with the ServiceKnownType attribute for each of the classes.
There is another option, which is to implement the generic lists in classes that are attributed with CollectionDataContract:
[CollectionDataContract]
public class Table1Collection
Inherits List<Table1>
On the client side, you can the edit Reference.svcmap and enumerate each of the collections in the CollectionMappings section:
<CollectionMappings>
<CollectionMapping TypeName="My.Namespace.Table1Collection" Category="List" />
This allows you to reuse the same code on both ends of the pipe.
I have a site that give me this xml response on my GET request:
<ServerUnits>
<State Name="ServerName" Date="2008-04-01" >
<Users>
<User login="someUser1" Password="123456">
<User login="someUser2" Password="qwerty">
</Users>
</ServerUnits>
I want use WCF Client for work with this service.
How to discraibe Message Contract of this response for WCF Clien
It is best to create client proxies for the WCF service. It will create the data contracts for you (as mentioned by #Aliostad) so you don't have to create them manually. To do this right click on your solution and select "Add Service Reference..." from the context-menu and enter the address to your WCF service.
I think that WCF is not useful is your case.
A more simple way would be to write objects that match this xml response and just deserialize xml stream onto objects instances.
What you have posted is not a SOAP message so MessageContract is not appropriate.
I imagine what you posted is the SOAP body content so you need to do something along the line of this:
[DataContract]
public class ServerUnits
{
[DataMember]
public ServerState State { get; set; }
[DataMember]
public List<User> Users { get; set; }
}
[DataContract]
public class ServerState
{
[DataMember]
public string Name { get; set; }
[DataMember]
public DateTime Date { get; set; }
}
[DataContract]
public class User
{
[DataMember]
public string login { get; set; }
[DataMember]
public string password { get; set; }
}
UPDATE
Your message is not SOAP. But you can still use the code above if you use webHttpBinding which sends and receives POX.
I have created two WCF Services (Shipping & PDFGenerator). They both, along with my ClientApp, share an assembly named Kyle.Common.Contracts. Within this assembly, I have three classes:
namespace Kyle.Common.Contracts
{
[MessageContract]
public class PDFResponse
{
[MessageHeader]
public string fileName { get; set; }
[MessageBodyMember]
public System.IO.Stream fileStream { get; set; }
}
[MessageContract]
public class PDFRequest
{
[MessageHeader]
public Enums.PDFDocumentNameEnum docType { get; set; }
[MessageHeader]
public int? pk { get; set; }
[MessageHeader]
public string[] emailAddress { get; set; }
[MessageBodyMember]
public Kyle.Common.Contracts.TrackItResult[] trackItResults { get; set; }
}
[DataContract(Name = "TrackResult", Namespace = "http://kyle")]
public class TrackResult
{
[DataMember]
public int SeqNum { get; set; }
[DataMember]
public int ShipmentID { get; set; }
[DataMember]
public string StoreNum { get; set; }
}
}
My PDFGenerator ServiceContract looks like:
namespace Kyle.WCF.PDFDocs
{
[ServiceContract(Namespace="http://kyle")]
public interface IPDFDocsService
{
[OperationContract]
PDFResponse GeneratePDF(PDFRequest request);
[OperationContract]
void GeneratePDFAsync(Kyle.Common.Contracts.Enums.PDFDocumentNameEnum docType, int? pk, string[] emailAddress);
[OperationContract]
Kyle.Common.Contracts.TrackResult[] Test();
}
}
If I comment out the GeneratePDF stub, the proxy generated by VS2010 realizes that Test returns an array of Kyle.Common.Contracts.TrackResult. However, if I leave GeneratePDF there, the proxy refuses to use Kyle.Common.Contracts.TrackResult, and instead creates a new class, ClientApp.PDFDocServices.TrackResult, and uses that as the return type of Test.
Is there a way to force the proxy generator to use Kyle.Common.Contracts.TrackResult whenever I use a MessageContract? Perhaps there's a better method for using a Stream and File Name as return types?
I just don't want to have to create a Copy method to copy from ClientApp.PDFDocServices.TrackResult to Kyle.Common.Contracts.TrackResult, since they should be the exact same class.
After a lot of extra digging, I realize that it was actually the Enum that "broke" it. It has do with the way DataContractSerializer works vs. XmlSerializer. Long story short, the solution was to turn the Enum into a nullable.
[MessageContract]
public class PDFRequest
{
[MessageHeader]
public Enums.PDFDocumentNameEnum? docType { get; set; }
[MessageHeader]
public int? pk { get; set; }
[MessageHeader]
public string[] emailAddress { get; set; }
[MessageBodyMember]
public Kyle.Common.Contracts.TrackItResult[] trackItResults { get; set; }
}
I ran into the same problem (MessageContract+enums) and your post helped me. Indeed if you explicitly set the enum fields to nullable it works. The issue is that when enums are used, WCF uses the XML serializer which cannot tell null from empty string.
There is a detailed explanation of this behaviour here by one of the actual WCF team members.
In the case of document/literal when using bare messages, WCF falls back to XmlSerializer when handling enum types. ... XmlSerializer treats null as missing by default ... we encounter a schema without nillable="true" ... The detection logic for value types currently only handles primitive value types, it does not check for enums.
In other words WCF does not like enums... But hey, it works, you just need to be aware of it!
You can instruct Visual Studio to re-use classes from referenced assemblies. So if your test project has an assembly reference to the Kyle.Common.Contracts assembly, it should re-use those types defined in there rather than adding new client-side proxy classes.
The switch to enable this is on the Advanced page in the Add Service Reference dialog window - it should be on by default:
Make sure that your project
has an assembly reference to the common data contract assembly
that this setting is really ON when you add the service reference