Sending a SOAP request containing an empty namespace - wcf

I am trying to submit data through a SOAP request. The data received in the web service is null, presumably because the blank xmlns namespace in the XML that I am sending causes the the data to return null (e.g. the TransmissionVersion). Most of the online resources recommend removing xmlns = "", but is there a quick way to make the service accept the namespace without having to inspect it and remove it before the request is sent, as is done here?
https://cmatskas.com/changing-soap-message-data-and-namespaces-with-c/
<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" xmlns:efil="http://samplelink.org" xmlns="http://samplelink.org">
<soapenv:Header/>
<soapenv:Body>
<Send xmlns="http://samplelink.org">
<Transmission>
<SimplifiedReturnTransmission transmissionVersion="2015V01" xmlns="">
<TransmissionHeader>
<TransmissionId>TI000024691218803</TransmissionId>
<Timestamp>2014-09-07T10:18:26</Timestamp>
<Transmitter>
<ETIN>67666665</ETIN>
</Transmitter>
<ProcessType>T</ProcessType>
<DocumentCount>1</DocumentCount>
<TransmissionPaymentHash>14022.49</TransmissionPaymentHash>
</TransmissionHeader>
</SimplifiedReturnTransmission>
</Transmission>
</Send>
</soapenv:Body>
</soapenv:Envelope>
Here's the relevant code from the interface:
<DataContract()>
<System.Xml.Serialization.XmlRootAttribute("SimplifiedReturnTransmission")>
Public Class SimplifiedReturnTransmissionType
Private transmissionHeaderField As TransmissionHeaderType
Private simplifiedReturnDocumentField() As SimplifiedReturnDocumentType
Private transmissionVersionField As String
<DataMember(EmitDefaultValue:=False)>
<System.Xml.Serialization.XmlElementAttribute("TransmissionHeader")>
Public Property TransmissionHeader() As TransmissionHeaderType
Get
Return Me.transmissionHeaderField
End Get
Set(ByVal value As TransmissionHeaderType)
Me.transmissionHeaderField = value
End Set
End Property
<DataMember(EmitDefaultValue:=False)> <System.Xml.Serialization.XmlAttributeAttribute()>
Public Property transmissionVersion() As String
Get
Return Me.transmissionVersionField
End Get
Set(ByVal value As String)
Me.transmissionVersionField = value
End Set
End Property
End Class

Making the element with a blank namespace on it an ElementAttribute rather than a RootAttribute resolved the issue. The parent of that element became a root attribute instead.
<System.Xml.Serialization.XmlElementAttribute("SimplifiedReturnTransmission")>
<System.Xml.Serialization.XmlRootAttribute("Transmission", [Namespace]:="", IsNullable:=False)>

Related

WCF error management using WebFaultException - Wrong HttpStatusCode

I have implemented a simple web service and I want to return appropriate Http Status Error codes depending on the error that occurred in the method being called.
I am using .NET framework 4.7.2 with Visual Studio 2019 and the IIS Express 10 built into Visual Studio for testing at the moment. I am using the Boomerang extension for Chrome to call the service.
I have implemented a FindPerson method that takes a name. If the person is not found, or more than one person is found, I want to return a "Not Found" response (404).
I have implemented a simple ServiceError class that I am using to throw a WebFaultException along with a Not Found error code. When I throw the WebFaultException the appropriate fault response is sent to the consumer (I see the details of the problem) but the http status is 500 (internal service error) instead of the 404 error I used (and expected to be received)
Here is my simple FindPerson method:
Private Function FindPerson(ByVal name As String) As Person Implements MyService.FindPerson
Dim foundPerson As Person = Nothing
Dim people = GetPeople(name)
Select Case people.Count
Case Is > 1
Throw New WebFaultException(Of ServiceError)(New ServiceError("More than 1 person found with the name provided.", ""), HttpStatusCode.NotFound)
Case Is = 0
Throw New WebFaultException(Of ServiceError)(New ServiceError("No Person with the name provided was found.", ""), HttpStatusCode.NotFound)
Case Else
foundPerson = people(0)
End Select
Return foundPerson
End Function
Here is my ServiceError class:
<DataContract>
Public Class ServiceError
<DataMember>
Public Property ErrorInfo As String
<DataMember>
Public Property ErrorDetails As String
Public Sub New(ByVal info As String, ByVal details As String)
Me.ErrorInfo = info
Me.ErrorDetails = details
End Sub
Public Sub New()
End Sub
End Class
This is the response I get. Like I said, the details are correct... but the Http Status Error code is 500 instead of 404:
<s:Envelope
xmlns:s="http://www.w3.org/2003/05/soap-envelope"
xmlns:a="http://www.w3.org/2005/08/addressing">
<s:Header>
<a:Action s:mustUnderstand="1">http://www.w3.org/2005/08/addressing/soap/fault</a:Action>
</s:Header>
<s:Body>
<s:Fault>
<s:Code>
<s:Value>s:Sender</s:Value>
<s:Subcode>
<s:Value
xmlns:a="http://schemas.microsoft.com/2009/WebFault">a:NotFound
</s:Value>
</s:Subcode>
</s:Code>
<s:Reason>
<s:Text xml:lang="en-US">Not Found</s:Text>
</s:Reason>
<s:Detail>
<ServiceError
xmlns="http://schemas.datacontract.org/2004/07/My_Web_Service"
xmlns:i="http://www.w3.org/2001/XMLSchema-instance">
<ErrorDetails/>
<ErrorInfo>No Person with the name provided was found.</ErrorInfo>
</ServiceError>
</s:Detail>
</s:Fault>
</s:Body>
</s:Envelope>
Edit:
I found a solution to my problem but it has only made me realize I need to find a better solution.
The solution involved implementing a method called RaiseWebException that set the outgoing response status code to the status that was also set in the WebFaultException:
Private Sub RaiseWebException(ByVal status As HttpStatusCode, ByVal info As String, ByVal details As String)
WebOperationContext.Current.OutgoingResponse.StatusCode = status
Throw New WebFaultException(Of ServiceError)(New ServiceError(info, details), HttpStatusCode.NotFound)
End Sub
I called the method when I failed to find my person:
Private Function FindPerson(ByVal name As String) As Person Implements MyService.FindPerson
Dim foundPerson As Person = Nothing
Dim people = GetPeople(name)
Select Case people.Count
Case Is > 1
RaiseWebException(HttpStatusCode.NotFound, "More than 1 person found with the name provided.", "")
Case Is = 0
RaiseWebException(HttpStatusCode.NotFound, "Person with the name provided was found.", "")
Case Else
foundPerson = people(0)
End Select
Return foundPerson
End Function
This works well when calling the method from the browser using Boomerang; however, when I test calling the method in a vb.net consuming test application, I am not seeing the service error details. I am getting the generic 404 error with a generic error message that states that "the endpoint couldn't be reached" instead of "the person wasn't found". This will be confusing for anyone calling the service through a consuming application implemented with .NET
I'm not sure this is the best solution for error management in my service going forward.
I am open to any suggestions on the best practices for error management in .NET web services.
Thank you
I found a solution to my original question: Set the OutGoingResponse.StatusCode to the same status code I used for the WebFaultException
The solution involved implementing a method called RaiseWebException that set the outgoing response status code to the status that was also set in the WebFaultException:
Private Sub RaiseWebException(ByVal status As HttpStatusCode, ByVal info As String, ByVal details As String)
WebOperationContext.Current.OutgoingResponse.StatusCode = status
Throw New WebFaultException(Of ServiceError)(New ServiceError(info, details), status)
End Sub
I call the method when I failed to find my person:
Private Function FindPerson(ByVal name As String) As Person Implements MyService.FindPerson
Dim foundPerson As Person = Nothing
Dim people = GetPeople(name)
Select Case people.Count
Case Is > 1
RaiseWebException(HttpStatusCode.NotFound, "More than 1 person found with the name provided.", "")
Case Is = 0
RaiseWebException(HttpStatusCode.NotFound, "Person with the name provided was found.", "")
Case Else
foundPerson = people(0)
End Select
Return foundPerson
End Function
This works well when calling the method from the browser using Boomerang; however, when I test calling the method in a vb.net consuming test application, I am not seeing the service error details. I am getting the generic 404 error with a generic error message that states that "the endpoint couldn't be reached" instead of "the person wasn't found". This will be confusing for anyone calling the service through a consuming application implemented with .NET.
So, while this is the answer to my original question/problem, it appears to be inadequate for the overall project. Best of luck to anyone else facing the same thing.

Problem deserializing a response from USPS CityStateLookup with Newtonsoft.Json.JsonConvert.DeserializeObject

I'm attempting to parse a response from the USPS CityStateLookup API and I don't appear to be modelling it properly, as I'm getting an "{"Unexpected character encountered while parsing value: <. Path '', line 0, position 0."}" while parsing" error right at the beginning of the DeserializeObject call
My code is:
Class CityStateLookupResponse
Property ZipCodeList As List(Of ZipCode)
End Class
Class ZipCode
Property Zip5 As String
Property City As String
Property State As String
End Class
Private Async Function GetCityStateFromZipAsync(byval Zip5Code as string) as threading.tasks.task(of CityStateLookupResult)
Dim result As New CityStateLookupResponse
Dim client As New HttpClient() With {
.BaseAddress = New Uri("http://production.shippingapis.com/ShippingAPI.dll")
}
Dim arguments As String = "?API=CityStateLookup&XML=<CityStateLookupRequest USERID=""{0}""><ZipCode ID= ""{1}""><Zip5>{2}</Zip5></ZipCode></CityStateLookupRequest>"
arguments = String.Format(arguments, "<My User ID>", 0, Zip5Code)
response = Await client.GetAsync(arguments)
If Not response.IsSuccessStatusCode Then
Return result
End If
myContent = Await response.Content.ReadAsStringAsync
' vvvv THIS IS THE ERROR LINE vvvv
result = Newtonsoft.Json.JsonConvert.DeserializeObject(Of CityStateLookupResponse)(myContent)
end function
The returned XML for the same API call in a browser is:
<CityStateLookupResponse>
<ZipCode ID="0">
<Zip5>55016</Zip5>
<City>COTTAGE GROVE</City>
<State>MN</State>
</ZipCode>
</CityStateLookupResponse>
What am I doing wrong in the class definition for CityStateLookupResponse? (Or is there a better way to go about this altogether?)
Haven't looked at VB in a while but it appears you are using the wrong method for deserializing XML. The method you are using is meant for JSON.
For XML deserialization use DeserializeXmlNode.

WCF- Sign a specific field inside the body of a soap message

I only need to sign one specific field inside a WCF message.
The class has the next aspect:
[System.Diagnostics.DebuggerStepThroughAttribute()]
[System.CodeDom.Compiler.GeneratedCodeAttribute("System.ServiceModel", "3.0.0.0")]
[System.ServiceModel.MessageContractAttribute(WrapperName="simpleInputData",
ProtectionLevel = ProtectionLevel.None
, IsWrapped=true)]
public partial class SimpleOperationRequest
{
[System.ServiceModel.MessageHeaderAttribute(
ProtectionLevel = ProtectionLevel.None)]
public BusinessHeader businessHeader;
[System.ServiceModel.MessageHeaderAttribute(
ProtectionLevel = ProtectionLevel.None)]
public TechnicalHeader technicalHeader;
[System.ServiceModel.MessageBodyMemberAttribute(
ProtectionLevel = ProtectionLevel.Sign, Order = 0)]
public SimpleInput simpleInput;
[System.ServiceModel.MessageBodyMemberAttribute(
ProtectionLevel = ProtectionLevel.None, Order = 1)]
public Attachment attachment;
[...]
}
As you can see, I only need sign simpleInput field, but, when I run the code, the package sent is (only show the body node):
[...]
<s:Body u:Id="_3" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<simpleInputData xmlns="http://xxxx/simple">
<simpleInput>
<in>llega?</in>
</simpleInput>
<attachment>
<ImageData>iVBORw0K...5CYII=</ImageData>
</attachment>
</simpleInputData>
</s:Body>
[...]
In the code, you can see that the whole body node is signed.
How could I obtain only the node "simpleInput" signed??
Thanks a lot in advance!
Not possible in WCF. You must sign the whole body or nothing of it. You could choose which headers to sign though.

VBA MSSOAP.SoapClient30 error: Incorrect number of parameters supplied for SOAP request HRESULT=0x80070057

update: so I've figured I need to somehow submit a complex type at method parameter - how do I do this with VBA?
This is my first time coding VBA and I will appreciate any possible pointers at how I can fix the problem. Basically, I've written a little soap service and it works fine - I test it with SoapUI - so I guess other application should be able to consume it.
The WSDL the service generates is here. Perhaps, it is not too friendly for consuming by VBScript SOAPClient - any points in that direction will help a lot.
I'm trying to put a bit of code together that actually uses it (VBScript below) - I've built it on top of an example I found while googling. It generates the following error:
Incorrect number of parameters supplied for SOAP request HRESULT=0x80070057
Module Module1
Dim WSDLFileName As String
Dim base64attachment As String
Dim attachment_filename As String
Dim summary As String
Dim SoapClient
Dim res
Sub Main()
WSDLFileName = "http://localhost:7777/?wsdl"
base64attachment = "UG9ydG1hbiBpcyBwb3J0Zm9saW8gbWFuYWdlbWVudCBzb2Z0d2FyZSB0byBoZWxwIFBNTyBrZWV"
attachment_filename = "test_file.txt"
summary = "test issue with summary"
SoapClient = CreateObject("MSSOAP.SoapClient30")
SoapClient.MSSoapInit(WSDLFileName)
res = SoapClient.CreateJiraIssueWithBase64Attachment(summary, base64attachment, attachment_filename)
Console.Out.WriteLine(res)
End Sub
End Module
Any pointers will help, I'm lost here.
I'm expecting it should create a response like this:
<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" xmlns:open="open.JiraAdapter">
<soapenv:Header/>
<soapenv:Body>
<open:CreateJiraIssueWithBase64Attachment>
<open:summary>some summary</open:summary>
<open:base64attachment>BASE64CODEDFILE</open:base64attachment>
<open:attachment_filename>NAME of the file attached</open:attachment_filename>
</open:CreateJiraIssueWithBase64Attachment>
</soapenv:Body>
</soapenv:Envelope>
Your service response contains complex type object.
<xs:element name="CreateJiraIssueWithBase64AttachmentResult" type="s0:Status" minOccurs="0" nillable="true"/>
To be able to use complex types you need to use "MSSOAP.SoapSerializer30" to create request and "MSSOAP.SoapReader30" for reading response.
SOAP UI can helps you to see the correct request structure (tags, namespaces and actions).
I think it something like that
Connector = CreateObject("MSSOAP.HttpConnector30")
Connector.Property("EndPointURL") = "url"
Connector.Property("UseSSL") = True
Connector.Connect
Connector.Property("SoapAction") = "CreateJiraIssueWithBase64Attachment"
Connector.BeginMessage
Serializer = CreateObject("MSSOAP.SoapSerializer30")
Serializer.Init(Connector.InputStream)
Serializer.StartEnvelope
Serializer.StartBody
Serializer.StartElement("CreateJiraIssueWithBase64Attachment";"open.jiraAdapter.test")
Serializer.StartElement("summary";"open.jiraAdapter.test")
Serializer.WriteString("another test issue for JUR")
Serializer.EndElement
Serializer.StartElement("base64attachment";"open.jiraAdapter.test")
Serializer.WriteString("Y29kZTogaHR0cDovL3Bhc3RlYmluLmNvbS9EbUx3N0oycQ0KeG1sOiBodHRwOi8vcGFzdGViaW4uY29tLzE3Q2MxVjJM")
Serializer.EndElement
Serializer.StartElement("attachment_filename";"open.jiraAdapter.test")
Serializer.WriteString("readme.txt")
Serializer.EndElement
Serializer.EndElement
Serializer.EndBody
Serializer.EndEnvelope
Connector.EndMessage
Reader = CreateObject("MSSOAP.SoapReader30")
Reader.Load(Connector.OutputStream)
/// Reader.Body.xml - response
Hope this help you.

How can I write to my own app.config using a strongly typed object?

The following code has two flaws, I can't figure out if they are bugs or by design. From what I have seen it should be possible to write back to the app.config file using the Configuration.Save and according to http://www.codeproject.com/KB/cs/SystemConfiguration.aspx the code should work.
The bugs are shown in the source below and appear when you try to set the property or save the config back out.
Imports System.Configuration
Public Class ConfigTest
Inherits ConfigurationSection
<ConfigurationProperty("JunkProperty", IsRequired:=True)> _
Public Property JunkProperty() As String
Get
Return CStr(Me("JunkProperty"))
End Get
Set(ByVal value As String)
' *** Bug 1, exception ConfigurationErrorsException with message "The configuration is read only." thrown on the following line.
Me("JunkProperty") = value
End Set
End Property
Public Sub Save()
Dim ConfigManager As Configuration = ConfigurationManager.OpenExeConfiguration(ConfigurationUserLevel.None)
' The add / remove is according to http://www.codeproject.com/KB/cs/SystemConfiguration.aspx
ConfigManager.Sections.Remove("ConfigTest")
' *** Bug 2, exception InvalidOperationException thrown with message "Cannot add a ConfigurationSection that already belongs to the Configuration."
ConfigManager.Sections.Add("ConfigTest", Me)
ConfigManager.Save(ConfigurationSaveMode.Full, True)
End Sub
Public Shared Sub Main()
Dim AppConfig As ConfigTest = TryCast(ConfigurationManager.GetSection("ConfigTest"), ConfigTest)
AppConfig.JunkProperty = "Some test data"
AppConfig.Save()
End Sub
' App.Config should be:
' <?xml version="1.0" encoding="utf-8" ?>
'<configuration>
' <configSections>
' <section name="ConfigTest" type="ConsoleApp.ConfigTest, ConsoleApp" />
' </configSections>
' <ConfigTest JunkProperty="" />
'</configuration>
End Class
I'd like to do it this way so that on the first run of the app I check for the properties and then tell the user to run as admin if they need to be set, where the UI would help them with the settings. I've already 'run as admin' to no effect.
Your code doesn't really make any sense. I took your example code and turned it into a simple example that works. Please note this is not best practise code, merely an example to aid you on your journey of learning the configuration API.
Public Class ConfigTest
Inherits ConfigurationSection
<ConfigurationProperty("JunkProperty", IsRequired:=True)> _
Public Property JunkProperty() As String
Get
Return CStr(Me("JunkProperty"))
End Get
Set(ByVal value As String)
' *** Bug 1, exception ConfigurationErrorsException with message "The configuration is read only." thrown on the following line.
Me("JunkProperty") = value
End Set
End Property
Public Overrides Function IsReadOnly() As Boolean
Return False
End Function
Public Shared Sub Main()
Dim config As Configuration = ConfigurationManager.OpenExeConfiguration(ConfigurationUserLevel.None)
Dim AppConfig As ConfigTest = config.GetSection("ConfigTest")
AppConfig.JunkProperty = "Some test data"
config.Save()
End Sub
End Class
This code will open the config file, modify the attribute JunkProperty and persist it back it the executable's configuration file. Hopefully this will get you started- it looks like you need to read about the configuration API a bit more.
I've used the API to create configuration sections for large scale enterprise apps, with several 1000 of lines of custom hierarchical config (my config was readonly though). The configuration API is very powerful once you've learnt it. One way I found out more about its capabilities was to use Reflector to see how the .NET framework uses the API internally.
Maybe you don't know Portuguese or c# but this is you want http://www.linhadecodigo.com.br/Artigo.aspx?id=1613
using BuildProvider from asp.net
After loading a configuration it is readonly by default, principally because you have not overriden the IsReadOnly property. Try to override it.
¿Is there something that prevents you from using a setting?
Looks like it is not possible by design. App.config is normally protected as it resides along with the app in the Program Files directory so must be amended at installation time by the installer.
Pity really, I'd like the app to have settings that an admin can set.
Sorry if I didn't understand your case, but yes, you can change App.config at runtime.
Actually, you will need to change YourApp.exe.config, because once your app is compiled, App.config contents are copied into YourApp.exe.config and your application never looks back at App.config.
So here's what I do (C# code - sorry, I still haven't learnt VB.Net)
public void UpdateAppSettings(string key, string value)
{
XmlDocument xmlDoc = new XmlDocument();
xmlDoc.Load(AppDomain.CurrentDomain.SetupInformation.ConfigurationFile);
foreach (XmlElement item in xmlDoc.DocumentElement)
{
foreach (XmlNode node in item.ChildNodes)
{
if (node.Name == key)
{
node.Attributes[0].Value = value;
break;
}
}
}
using (StreamWriter sw = new StreamWriter(AppDomain.CurrentDomain.SetupInformation.ConfigurationFile))
{
xmlDoc.Save(sw);
}