WCF DataContract properties - c#

I noticed using SoapUI that the DataContract (request) parameter of my OperationContract is considered "optional". (I also tested it and in fact i can avoid to pass the request at my wcf method, or pass it as null)
<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" xmlns:con="..." xmlns:data="...">
<soapenv:Header/>
<soapenv:Body>
<con:MyMethod>
<!--Optional:-->
<con:request>
<data:Id>?</data:Id>
</con:request>
</con:MyMethod>
</soapenv:Body>
</soapenv:Envelope>
On a DataMember, like Id in the example, i can put the attributes
[DataMember(IsRequired = true, EmitDefaultValue = false)]
public string Id {get; set;}
In this case if I don't put at all the xml Id part, or I put it has null I get a serializzation exception as response, and my OperatationContract method is never called, and therefore i can be sure in the implementation of my method that Id will be != null.
I don't know how to do this with the request itself though.
The easy solution would be check in every Operation method if the request is null and in that case return an error message. I hope that there is a more versatile solution though

Related

Implement SOAP 1.2 service in asp.net core

I'm trying to implement OCPP 1.5 under ASP.Net Core 2.2. The standard makes use of SOAP 1.2. The issue comes from poor interpretation of the MessageHeader attribute. Properties with MessageHeader should be in header, but they are not.
Source code: https://github.com/delianenchev/OcppDemo
I use SoapCore under ASP.Net Core. My initialization looks like this:
var transportBinding = new HttpTransportBindingElement();
var textEncodingBinding = new TextMessageEncodingBindingElement(MessageVersion.Soap12WSAddressingAugust2004, System.Text.Encoding.UTF8);
var customBinding = new CustomBinding(transportBinding, textEncodingBinding);
app.UseSoapEndpoint<ISampleService>("/SOAP/service.wsdl", customBinding, SoapSerializer.DataContractSerializer);
My demo model with the MessageHeader and MessageBodyMember attributes.
[MessageContract]
public class MessageModelRequest
{
[MessageHeader]
public string Id { get; set; }
[MessageBodyMember]
public string Name { get; set; }
[MessageBodyMember]
public string Email { get; set; }
}
I test API with SoapUI.
This is my API under ASP.NET core with SoapCore.
<soap:Envelope xmlns:soap="http://www.w3.org/2003/05/soap-envelope" xmlns:tem="http://tempuri.org/" xmlns:mod="http://schemas.datacontract.org/2004/07/Models">
<soap:Header/>
<soap:Body>
<tem:TestMessageModel>
<!--Optional:-->
<tem:inputModel>
<!--Optional:-->
<mod:Email>?</mod:Email>
<!--Optional:-->
<mod:Id>1</mod:Id>
<!--Optional:-->
<mod:Name>?</mod:Name>
</tem:inputModel>
</tem:TestMessageModel>
</soap:Body>
</soap:Envelope>
Correct API from WCF project for IIS.
<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" xmlns:tem="http://tempuri.org/">
<soapenv:Header>
<tem:Id>34</tem:Id>
</soapenv:Header>
<soapenv:Body>
<tem:MessageModelRequest>
<!--Optional:-->
<tem:Email>3</tem:Email>
<!--Optional:-->
<tem:Name>4</tem:Name>
</tem:MessageModelRequest>
</soapenv:Body>
</soapenv:Envelope>
Well, judging by the source code of SoapCore it seems to support message headers for reading the SOAP message as it uses MessageEncoder for that purpose which knows exactly how to read a SOAP message, but when it comes to serializing a response in your case it uses a native DataContractSerializer for writing the body that ignores any message contract attributes you have on your class and furthermore it doesn't have any part for writing header, just the message body.
So I guess you need to implement the header support in response messages by yourself.
First of all, add IgnoreMemberAttribute (or XmlIgnoreAttribute if you switch to SoapSerializer.XmlSerializer) on the properties you intend to add to your response message header so that data contract serializer doesn't add them to the body of the message.
Finally, you will need to locate the properties decorated with MessageHeader attribute manually and add them to your header. Luckily SoapCore has multiple options for doing that as suggested here.
As alternative if you plan to include the source of SoapCore in your solution, you could easily achieve the goal somewhere along these lines. It's easy to do so because at this place you have the full control of the message and the response you got from your service method. With the aid of reflection, you can easily find the properties of responseObject which need to be moved to the header and just forward them to responseMessage.Headers.
I know it's a bit nasty, but well... this is the price of using SOAP in .NET Core.
In .NET 6, you can, for example, do the following to switch to SOAP 1.2 (a.k.a. soap12):
// In Program.cs ...
var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();
// ...
app.UseSoapEndpoint<IMyServiceContract>("/MyService.asmx", new SoapEncoderOptions()
{
// Use SOAP version 1.2 (aka Soap12)
MessageVersion = MessageVersion.Soap12WSAddressingAugust2004
// - OR -
MessageVersion = MessageVersion.Soap12WSAddressing10
}, caseInsensitivePath: true);
That will result in:
<wsdl:definitions xmlns:soap12="http://schemas.xmlsoap.org/wsdl/soap12/" ...>

Converting SOAP XMLString to C# object

[Question Modified] I am completely new to XML and am calling a web service(WCF). The request is expected to be in the below format:
<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" xmlns:tem="http://tempuri.org/">
<soapenv:Header/>
<soapenv:Body>
<tem:CreateLead>
<!--Optional:-->
<tem:xDoc>
<lead>
<![CDATA[<CreateLead>
<FIRSTNAME>John</FIRSTNAME>
<LASTNAME>Doe</LASTNAME>
<MOBILE>9999999999</MOBILE>
<EMAIL>john#doe.com</EMAIL>
<SOURCE>Website</SOURCE>
<SUBSOURCE>Blog</SUBSOURCE>
<WEBREMARKS></WEBREMARKS>
<TIMETOCALL>26/02/2018 18:40:15 PM</TIMETOCALL>
<FAILEDSTEP>Step 1</FAILEDSTEP>
<FOILIONO>123</FOILIONO>
<SCHEMECODE>123</SCHEMECODE>
<AMOUNT>100</AMOUNT>
<REGEVENT></REGEVENT>
<FEEDBACK></FEEDBACK>
</CreateLead>
]]>
</lead>
</tem:xDoc>
</tem:CreateLead>
</soapenv:Body>
</soapenv:Envelope>
I have created the proxy class and the below is the signature of the method to be called:
public System.Xml.XmlElement CreateLead(System.Xml.XmlElement xDoc)
{
return base.Channel.CreateLead(xDoc);
}
I am passing the complete SOAP as a string literal and converting it into an XMLElement and am getting a proper response. I wish to pass it as a C# object. Is there a way to create a class according to my above SOAP request?
I tried copying the XML and using Visual Studio's paste special to Paste XML as class but it is somehow not creating an exact replica of the above mentioned SOAP XML.
I need it to be completely similar to the above SOAP format.
Please help.

How Do I Change XML Prefix For WCF Client Request c#?

Due to requirements from a web service that I have no control over, I need to change my ns prefixes from the defaults of "s" and "h". The service provider has provided a .wsdl file for my service reference, however, they do not appear to have an online wsdl for metadata exchange. I have emailed them and they confirmed that the prefixes have to be specific values (right or wrong on their part, I have to make this work).
Can someone please tell me the easiest way to change these values? Is there something I can modify in Reference.cs? Do I have to implement some sort of message formatting logic into my requests to change the values just prior to sending out the request?
Every element in the XML has to have a prefix of a specific value (2 values total), or the WS will not accept the request and simply returns HTML in the response. When I copy the request captured in Fiddler, over to SOAP UI and update the prefixes to what they require, the request works fine when executed from Soap UI. I'm simply not finding any simple solutions to this seemingly simple problem for c# VS 2017 development platform.
Also worth noting, when I load the .wsdl file into Soap UI, Soap UI generates all of the ws operations and requests with the correct, desired prefixes that I need in my c# app. How does Soap UI know the exact prefixes to generate in the requests?
I need to modify this:
<s:Envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/">
<s:Header>
<h:FooListHeader xmlns:h="http://foo.foo.com/Hello" xmlns:i="http://www.w3.org/2001/XMLSchema-instance">
<h:FooList>
<h:FooStatement>
<h:Title>Foo</h:Title>
<h:Value>123</h:Value>
</h:FooStatement>
</h:FooList>
</h:FooListHeader>
</s:Header>
<s:Body>
<GetFooRequestType xmlns="http://foo.foo.com/Hello">
<MessageRequest xmlns:i="http://www.w3.org/2001/XMLSchema-instance">
<ConFooRequest/>
</MessageRequest>
</GetFooRequestType>
</s:Body>
</s:Envelope>
To something like this:
<soapenv:Envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/" xmlns:foo="http://foo.foo.com/Hello">
<soapenv:Header>
<foo:FooListHeader xmlns:i="http://www.w3.org/2001/XMLSchema-instance">
<foo:FooList>
<foo:FooStatement>
<foo:Title>Foo</foo:Title>
<foo:Value>123</foo:Value>
</foo:FooStatement>
</foo:FooList>
</foo:FooListHeader>
</soapenv:Header>
<soapenv:Body>
<foo:GetFooRequestType xmlns="http://foo.foo.com/Hello">
<foo:MessageRequest xmlns:i="http://www.w3.org/2001/XMLSchema-instance">
<foo:ConFooRequest/>
</foo:MessageRequest>
</foo:GetFooRequestType>
</soapenv:Body>
</soapenv:Envelope>
Example c# code:
public GetYaddaYaddaResponseType CheckConnection()
{
Yadda client = new Yadda();
var msgRequest = new GetYaddaYaddaResponseType { ConnectionConfirmationRequest = new ConfirmConnectRequestType() };
List<Statement> statementList = new List<Statement>
{
new Statement { Name = "Yo", Value = "123" },
new Statement { Name = "Hey", Value = "123" },
};
var header = new StatementHeader
{
StatementList = statementList.ToArray()
};
var response = client.GetYaddaYaddaMessage(ref header, msgRequest);
return response;
}

Retrieve Inner XML in WCF from XMLHTTP POST Request

Hi and good day everyone,
I am able to send XML HTTP POST Request to WCF after solving it through this topic: Handle POST request from XML HTTP in WCF
But right now, I am still trying to find right way to retrieve inner XML from the request. I send the request in XML form :
<?xml version=""1.0"" encoding=""UTF-8"" standalone= ""yes""?><AFISQuery transid=""3356434""><Request CIFNO =""1234567890123456789"" IC= ""770707-07-7777"">TEST</Request></AFISQuery>
In the AFISQuery class, I set it as:
[DataContract(Namespace = "")]
public class AFISQuery
{
public AFISQuery(string transid)
{
this.transid = transid;
}
[DataMember]
public string Request { get; set; }
[DataMember]
public string transid { get; set; }
}
As the result, I am able to get Request information ("TEST"), but could not get transid information. I tried to look around the forums but failed to find similar problems.
My question is, would it be possible to retrieve the information from the inner XML of the request? For this instance, they are CIFNo, transid and ICNo.
Thanks in advance :)
The reason you're able to retrieve Request is that Request is an XML element. XML elements map directly to DataMembers, and as a result, Request is deserialized properly to the Request DataMember on the AFISQuery data contract type.
The problem with 'transid' is that it's an attribute. Attributes are not supported with DataContractSerializer, and they cannot in any way be supported. You need to change your request XML so that it has transid as an element, just like Request is an element.
Note also that even though Request is set to a value for you right now, the attributes on the "Request" element in the XML you have are totally ignored. You may or may not be OK with this.
If you care about attributes, or if you do want to support serializable members as attributes, you may need to switch to XmlSerializer from DataContractSerializer. It's easy to do this, just decorate the services or operations you care about -- that you want to switch over to XmlSerializer -- with [XmlSerializerFormat.]

How to call GetChanges() method from SiteData.asmx web service (SharePoint 2010)?

I'm trying to run GetChanges method (sitedata.asmx) from a Java application. However I can't figure out the correct parameters I must pass. This is for SharePoint 2010.
By checking on the service protocol specification, I saw this are the required parameters:
objectType: The change tracking space
to report about, either
"ContentDatabase" or "SiteCollection".
All other objectType values, as
defined in section 2.2.5.3, MUST NOT
be used. Note that "Site" in the
context of this parameter actually
means site collection.
contentDatabaseId: GUID of the content
database, known in advance or obtained
by GetContent request.
LastChangeId: A token specifying the starting point
for the requested change report.
Normally the protocol client obtains
this value from the response to a
previous GetContent or GetChanges operation.
CurrentChangeId: A token specifying
the endpoint for the requested change
report. If not empty, CurrentChangeId
must be a valid token obtained from
the response to a previous GetChanges
operation. Normally, this element is
empty; empty specifies that the
protocol client requests all changes
starting from the starting point up to
the present time.
Timeout: A value
that determines how many changes
should be fetched in the current
operation. This value MUST be greater
than 0 and the protocol server MUST
only fetch x% of total changes that
are fetched by default, where x is
(Timeout divided by 30000).
The protocol client MUST pass tokens that
correspond to the change tracking
space specified by the objectType and
the target URL of the SOAP request.
The SOAP In message I'm sending is as follows:
<?xml version='1.0' encoding='UTF-8'?>
<soapenv:Envelope xmlns:soapenv="http://www.w3.org/2003/05/soap-envelope">
<soapenv:Body>
<ns1:GetChanges xmlns:ns1="http://schemas.microsoft.com/sharepoint/soap/">
<ns1:objectType>SiteCollection</ns1:objectType>
<ns1:contentDatabaseId>E5C5E20A-5A9F-406C-B9F6-28923750CECD</ns1:contentDatabaseId>
<ns1:startChangeId>1;0;E5C5E20A-5A9F-406C-B9F6-28923750CECD;634438121498470000;46852</ns1:startChangeId>
<ns1:Timeout>0</ns1:Timeout>
</ns1:GetChanges>
</soapenv:Body>
</soapenv:Envelope>
However I get this response:
<?xml version="1.0" encoding="utf-8"?>
<soap:Envelope xmlns:soap="http://www.w3.org/2003/05/soap-envelope" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<soap:Body>
<soap:Fault>
<soap:Code>
<soap:Value>soap:Receiver</soap:Value>
</soap:Code>
<soap:Reason>
<soap:Text xml:lang="en">Exception of type 'Microsoft.SharePoint.SoapServer.SoapServerException' was thrown.</soap:Text>
</soap:Reason>
<detail>
<errorstring xmlns="http://schemas.microsoft.com/sharepoint/soap/">Object reference not set to an instance of an object.</errorstring>
</detail>
</soap:Fault>
</soap:Body>
</soap:Envelope>
Checked the logs from SharePoint (located at Program Files\Common Files\Microsoft Shared\Web Server Extensions\14\LOGS) and found the following exception:
SOAP exception: System.NullReferenceException: Object reference not set to an instance of an object.
at Microsoft.SharePoint.SPChangeToken.ParseChangeToken(String strChangeToken)
at Microsoft.SharePoint.SPChangeToken..ctor(String strChangeToken)
at Microsoft.SharePoint.SoapServer.SiteDataImpl.GetChanges(ObjectType objectType, String contentDatabaseId, String& startChangeId, String& endChangeId, Int64 maxChangesToFetch, UInt32 maxSPRequests, Boolean getMetadata, Boolean ignoreSecurityIfInherit, Int32 schemaVersion, Boolean& moreChanges)
at Microsoft.SharePoint.SoapServer.SiteDataImpl.GetChanges(ObjectType objectType, String contentDatabaseId, String& startChangeId, String& endChangeId, Int32 Timeout, Boolean& moreChanges)
at Microsoft.SharePoint.SoapServer.SiteData.GetChanges(ObjectType objectType, String contentDatabaseId, String& LastChangeId, String& CurrentChangeId, Int32 Timeout, Boolean& moreChanges)
However, I'm not able to find any references to that error. I can't even found the method ParseChangeToken from SPChangeToken class (http://msdn.microsoft.com/en-us/library/microsoft.sharepoint.spchangetoken_methods.aspx), so this is confusing.
I already saw this question, however this doesn't solve my issue: Other question
Can anyone help me calling this web service correctly?
EDIT
Tried calling it from a C# application to determine that the issue is not with Java. This is the code:
SiteData.SiteDataSoapClient siteDataService = new SiteData.SiteDataSoapClient();
siteDataService.Endpoint.Address = new System.ServiceModel.EndpointAddress("URL/_vti_bin/sitedata.asmx");
siteDataService.ClientCredentials.Windows.ClientCredential = new System.Net.NetworkCredential("username", "password", "domain");
siteDataService.ClientCredentials.Windows.AllowedImpersonationLevel = System.Security.Principal.TokenImpersonationLevel.Impersonation;
String startChangeId = "1;1;69d025ce-96a7-4131-adc0-7da1603e8d24;634439002539570000;46914";
String endChangeId = "";
bool hasMoreChanges = false;
String databaseID = E5C5E20A-5A9F-406C-B9F6-28923750CECD; //Got it by querying SharePoint database. Any idea how to get it programatically?
String result = siteDataService.GetChanges(SiteData.ObjectType.SiteCollection, databaseID, ref startChangeId, ref endChangeId, 0, out hasMoreChanges);
return result;
However, I got 'Microsoft.SharePoint.SoapServer.SoapServerException' and the detail of this exception is null. Used Fiddler to spy on the XML returned by the SharePoint server, and found the same 'Object reference not set to an instance of an object' exception.
So this certainly means there is something wrong with the parameters I'm passing, right?
Thanks!!
Edit
If someone is interested, I made this work too by setting StartChangeId to LastChangeId and EndChangeId to CurrentChangeId in the XML message.
Solved it. By checking on the SharePoint logs, I noticed the following lines:
06/20/2011 08:24:03.80 w3wp.exe (0x1C2C) 0x0CAC SharePoint Foundation General fbs6 Medium <?xml version="1.0" ?><S:Envelope xmlns:S="http://schemas.xmlsoap.org/soap/envelope/"><S:Body><GetChanges xmlns="http://schemas.microsoft.com/sharepoint/soap/"><objectType>SiteCollection</objectType><contentDatabaseId>{E5C5E20X-5A9F-406C-B9F6-28923750CECD}</contentDatabaseId><startChangeId></startChangeId><endChangeId>1;1;69c025ce-96a7-4131-adc0-7da1603e8d24;634439772069030000;47449</endChangeId><Timeout>0</Timeout></GetChanges></S:Body></S:Envelope> bafe1d43-e41c-47e9-bff2-5dc35a15298d
06/20/2011 08:24:03.80 w3wp.exe (0x1C2C) 0x0CAC SharePoint Foundation General 9ka5 Verbose GetChanges: objectType=SiteCollection, contentDbId={E5C5E20X-5A9F-406C-B9F6-28923750CECD}, startChange=, endChange=; MaxChanges=0, MaxSPRequests=50 bafe1d43-e41c-47e9-bff2-3dc35a15298d
Notice on the second line, that the content database Id is enclosed by "{}" characters. Also, see that "contentDbId" is parsed correctly from the incoming XML, while "endChange" is empty. This second observation, is probably what leads to the "Object reference not set to an instance of an object" exception. So, what is wrong with that changeId? No idea, probably there is something wrong with the XML encoding that prevents SharePoint from parsing the changeId correctly.
By further looking on the same log, I found this lines:
06/20/2011 08:42:54.35 w3wp.exe (0x1C2C) 0x2BC4 SharePoint Foundation General fbs6 Medium <?xml version='1.0' encoding='UTF-8'?><soapenv:Envelope xmlns:soapenv="http://www.w3.org/2003/05/soap-envelope"><soapenv:Body><ns1:GetChangesEx xmlns:ns1="http://schemas.microsoft.com/sharepoint/soap/"><ns1:version>1</ns1:version><ns1:xmlInput><GetChanges><ObjectType>1</ObjectType><ContentDatabaseId>{x4284f47-f050-4fe9-b7e9-caf8f4b882b0}</ContentDatabaseId><StartChangeId>1;0;x4284f47-f050-4fe9-b7e9-caf8f4b882b0;634441572386370000;72973</StartChangeId><EndChangeId /><RequestLoad>100</RequestLoad><GetMetadata>False</GetMetadata><IgnoreSecurityIfInherit>True</IgnoreSecurityIfInherit></GetChanges></ns1:xmlInput></ns1:GetChangesEx></soapenv:Body></soapenv:Envelope> fa5ab5a7-2e27-4e78-aa1f-b027ca3b120f
06/20/2011 08:42:54.35 w3wp.exe (0x1C2C) 0x2BC4 SharePoint Foundation General 9ka5 Verbose GetChanges: objectType=ContentDatabase, contentDbId={x4284f47-f050-4fe9-b7e9-caf8f4b882b0}, startChange=1;0;x4284f47-f050-4fe9-b7e9-caf8f4b882b0;634441572386370000;72973, endChange=; MaxChanges=500, MaxSPRequests=50 fa5ab5b7-2e27-4e78-aa1f-b027ca3b120f
Here, the changeId is correctly parsed from the incoming XML. So, I changed from GetChanges() method to GetChangesEx(), passed the exact same parameters I was using on the former call, and it worked correctly!! My guess is that because the parameters are encoded inside an element of the SOAP In request, the Web Service is able to parse them correctly.
Here is the final SOAP In message (formatted):
<?xml version='1.0' encoding='UTF-8'?>
<soapenv:Envelope xmlns:soapenv="http://www.w3.org/2003/05/soap-envelope">
<soapenv:Body>
<ns1:GetChangesEx xmlns:ns1="http://schemas.microsoft.com/sharepoint/soap/">
<ns1:version>1</ns1:version>
<ns1:xmlInput><GetChanges><ObjectType>7</ObjectType><ContentDatabaseId>{X5C5E20A-5A9F-406C-B9F6-28923750CECD}</ContentDatabaseId><StartChangeId>1;1;69f025ce-96a7-4131-adc0-7da1603e8d24;634439727021700000;47404</StartChangeId><EndChangeId>1;1;69d025ce-96a7-4131-adc0-7da1603e8b24;634441802456970000;47472</EndChangeId><RequestLoad>100</RequestLoad><GetMetadata>False</GetMetadata><IgnoreSecurityIfInherit>True</IgnoreSecurityIfInherit></GetChanges></ns1:xmlInput>
</ns1:GetChangesEx>
</soapenv:Body>
</soapenv:Envelope>
Edit
C# code example:
SiteData.SiteDataSoapClient siteDataService = new SiteData.SiteDataSoapClient();
siteDataService.Endpoint.Address = new System.ServiceModel.EndpointAddress("URL/_vti_bin/sitedata.asmx");
siteDataService.ClientCredentials.Windows.ClientCredential = new System.Net.NetworkCredential("username", "password", "domain");
siteDataService.ClientCredentials.Windows.AllowedImpersonationLevel = System.Security.Principal.TokenImpersonationLevel.Impersonation;
String xmlInput = "<GetChanges>" +
"<ObjectType>7</ObjectType>" +
"<ContentDatabaseId>{X5C5E20A-5A9F-406C-B9F6-28923750CECD}</ContentDatabaseId>" +
"<StartChangeId>1;1;69b025ce-96a7-4131-adc0-7da1603e8d24;634439727021700000;47404</StartChangeId>" +
"<EndChangeId>1;1;69b025ce-96a7-4131-adc0-7da1603e8d24;634441802456970000;47472</EndChangeId>" +
"<RequestLoad>100</RequestLoad>" +
"<GetMetadata>False</GetMetadata>" +
"<IgnoreSecurityIfInherit>True</IgnoreSecurityIfInherit>" +
"</GetChanges>";
String result = siteDataService.GetChangesEx(1, xmlInput);

Categories