Outgoing message from WCF client is incomplete when run outside of Visual Studio - wcf-client

I have a WCF client application written in VB.NET 2008 as a Windows forms application. This client app successfully communicates with a remote non-WCF service maintained by another company. The problem is - the communication is only successful when the client app is run from within Visual Studio (VS2008), not when it is run as a built executable. When the client app is run as an executable, the remote service returns this message:
"The HTTP request is unauthorized with client authentication scheme 'Anonymous'. The authentication header received from the server was ''. The remote server returned an error: (401) Unauthorized."
Digging a little deeper, I noticed the reason for this error. The message being sent to the remote service when the client app runs outside of VS is missing a section that it contains when run inside VS. The message sent when the app runs inside VS (i.e. the one that works properly) is shown below with sensitive information replaced with "x":
<HttpRequest xmlns="http://schemas.microsoft.com/2004/06/ServiceModel/Management/MessageTrace">
<Method>POST</Method>
<QueryString></QueryString>
<WebHeaders>
<VsDebuggerCausalityData>uIDPo6ppHQnHmDRGnZfDLPni6RYAAAAAaEkfl5VJXUauv5II8hPneT1AMwBfkoZNgfxEAZ2x4zQACQAA</VsDebuggerCausalityData>
<AUTHORIZATION>xxxxxxxxxxxxxxxxxxxxxxxxxxxx</AUTHORIZATION>
</WebHeaders>
</HttpRequest>
<s:Envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/">
<s:Header>
<Action s:mustUnderstand="1" xmlns="http://schemas.microsoft.com/ws/2005/05/addressing/none"></Action>
</s:Header>
<s:Body s:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<q1:getNewEvents_PPHS xmlns:q1="xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxins">
<loginObject href="#id1" xmlns=""></loginObject>
</q1:getNewEvents_PPHS>
<q2:LoginObject id="id1" xsi:type="q2:LoginObject" xmlns:q2="java:zoasis.ws.datamodel.general">
<clinicId xsi:type="xsd:int" xmlns="">xxxxx</clinicId>
<corporateId xsi:type="xsd:int" xmlns="">x</corporateId>
<password xsi:type="xsd:string" xmlns="">xxxxx</password>
<userName xsi:type="xsd:string" xmlns="">xxxx</userName>
</q2:LoginObject>
</s:Body>
</s:Envelope>
When running as a standalone executable, the client app sends out the same as above except that the entire HttpRequest section is missing - everything from <HttpRequest> to </HttpRequest>
Can anyone tell me why running the client app outside Visual Studio would cause the HttpRequest portion of the message to drop off? The app.config file is identical in both cases.
Thanks.
Per Mike's request, here is some more information. The client proxy was created using "Add Service Reference" in Visual Studio 2008.
The code that creates the message that is sent to the service is shown below in three parts.
The first part is a class called AntechServiceReference. It has two relevant methods. Its constructor builds the proxy that will be used to interact with the web service. The other method, called GetPendingDownloads, calls the web service method.
Imports WebServiceInterface.AntechServiceReference
Imports System.Configuration.ConfigurationManager
Imports System.ServiceModel
Imports System.ServiceModel.Security
Imports System.Text
Imports System.IO
Imports System.Xml
Public Class AntechLabDataAccess
' This class controls all data interaction with the remote Antech web service.
Private ClassName As String = "AntechLabDataAccess"
Private mErrText As String
Private mAntServProxy As ZoasisGroupServicesPortClient
Private mLoginObject As WebServiceInterface.AntechServiceReference.LoginObject
Private mLabEventIDs As WebServiceInterface.AntechServiceReference.LabAccessionIdObject()
Public Sub New()
Dim Action As String = ""
Dim CustomBehavior As MessageEndPointBehavior
Try
mErrText = ""
Action = "Creating proxy for web service. "
' Create a proxy to the remote web service for use in this object. Supply client credentials
' from app.config
mAntServProxy = New ZoasisGroupServicesPortClient("ZoasisGroupServicesPort")
' Retrieve access credentials for this web service from app.config.
Action = "Setting up login object. "
mLoginObject = New WebServiceInterface.AntechServiceReference.LoginObject
If Not AppSettings("ClinicID") Is Nothing Then
mLoginObject.clinicId = Integer.Parse(AppSettings("ClinicID"))
End If
If Not AppSettings("CorporateID") Is Nothing Then
mLoginObject.corporateId = Integer.Parse(AppSettings("CorporateID"))
End If
If Not AppSettings("Password") Is Nothing Then
mLoginObject.password = AppSettings("Password")
End If
If Not AppSettings("UserName") Is Nothing Then
mLoginObject.userName = AppSettings("UserName")
End If
' Add our custom behavior to the proxy. This handles creation of the message credentials
' necessary for web service authorization.
Action = "Adding custom behavior to the proxy. "
CustomBehavior = New MessageEndPointBehavior
mAntServProxy.Endpoint.Behaviors.Add(CustomBehavior)
Catch ex As Exception
mErrText = "Error caught in class [" & ClassName & "], method [New]. Action = " & Action & " Message = " & ex.Message & ". "
If Not ex.InnerException Is Nothing Then
mErrText &= "Additional Info: " & ex.InnerException.ToString & ". "
End If
Throw New Exception(mErrText)
End Try
End Sub
Public Sub GetPendingDownloads()
Dim Action As String = ""
Try
mErrText = ""
Action = "Calling getNewEvents_PPHS. "
mLabEventIDs = mAntServProxy.getNewEvents_PPHS(mLoginObject)
[catches are here]
End Try
End Sub
End Class
In addition to creating the proxy, the constructor above adds an endpoint behavior to it. That behavior is defined in the class shown next. The purpose of this behavior is to add a custom message inspector to inject authorization information into the HTTP header before the message is sent out:
Imports System.ServiceModel.Description
Public Class MessageEndPointBehavior
Implements IEndpointBehavior
' This class is used to make our custom message inspector available to the system.
Public Sub AddBindingParameters(ByVal endpoint As System.ServiceModel.Description.ServiceEndpoint, ByVal bindingParameters As System.ServiceModel.Channels.BindingParameterCollection) Implements System.ServiceModel.Description.IEndpointBehavior.AddBindingParameters
' Not Implemented
End Sub
Public Sub ApplyClientBehavior(ByVal endpoint As System.ServiceModel.Description.ServiceEndpoint, ByVal clientRuntime As System.ServiceModel.Dispatcher.ClientRuntime) Implements System.ServiceModel.Description.IEndpointBehavior.ApplyClientBehavior
' Add our custom message inspector to the client runtime list of message inspectors.
clientRuntime.MessageInspectors.Add(New MessageInspector())
End Sub
Public Sub ApplyDispatchBehavior(ByVal endpoint As System.ServiceModel.Description.ServiceEndpoint, ByVal endpointDispatcher As System.ServiceModel.Dispatcher.EndpointDispatcher) Implements System.ServiceModel.Description.IEndpointBehavior.ApplyDispatchBehavior
' Not Implemented
End Sub
Public Sub Validate(ByVal endpoint As System.ServiceModel.Description.ServiceEndpoint) Implements System.ServiceModel.Description.IEndpointBehavior.Validate
' Not Implemented
End Sub
End Class
The last piece of code is the custom message inspector itself:
Imports System.ServiceModel.Dispatcher
Imports System.ServiceModel.Channels
Imports System.Configuration.ConfigurationManager
Imports System.Text
Public Class MessageInspector
Implements IClientMessageInspector
' This class gives access to the outgoing SOAP message before it is sent so it can
' be customized.
Private mUserName As String
Private mPassword As String
Private mErrText As String
Public Sub New()
Dim CredentialsProvided As Boolean
CredentialsProvided = False
mUserName = AppSettings("CliCredUserName")
If Not mUserName Is Nothing Then
If mUserName.Trim <> "" Then
CredentialsProvided = True
End If
End If
If CredentialsProvided Then
CredentialsProvided = False
mPassword = AppSettings("CliCredPassword")
If Not mPassword Is Nothing Then
If mPassword.Trim <> "" Then
CredentialsProvided = True
End If
End If
End If
If CredentialsProvided Then
mUserName = mUserName.Trim
mPassword = mPassword.Trim
Else
Throw New Exception("This class (MessageInspector) requires information from the app.config file - specifically " _
& "AppSettings values for CliCredUserName and CliCredPassword. One or both of these is missing. ")
End If
End Sub
Public Sub AfterReceiveReply(ByRef reply As System.ServiceModel.Channels.Message, ByVal correlationState As Object) Implements System.ServiceModel.Dispatcher.IClientMessageInspector.AfterReceiveReply
' Not Implemented
End Sub
Public Function BeforeSendRequest(ByRef request As System.ServiceModel.Channels.Message, ByVal channel As System.ServiceModel.IClientChannel) As Object Implements System.ServiceModel.Dispatcher.IClientMessageInspector.BeforeSendRequest
Dim HTTPMsgHdr As HttpRequestMessageProperty
Dim objHTTPRequestMsg As Object = Nothing
Dim Auth As String = ""
Dim Action As String = ""
Dim BinaryData As Byte()
Try
Action = "Checking HTTP headers. "
If request.Properties.TryGetValue(HttpRequestMessageProperty.Name, objHTTPRequestMsg) Then
Action = "Changing existing HTTP header. "
HTTPMsgHdr = CType(objHTTPRequestMsg, HttpRequestMessageProperty)
If Not HTTPMsgHdr Is Nothing Then
If String.IsNullOrEmpty(HTTPMsgHdr.Headers("AUTHORIZATION")) Then
Auth = mUserName & ":" & mPassword
ReDim BinaryData(Auth.Length)
BinaryData = Encoding.UTF8.GetBytes(Auth)
Auth = Convert.ToBase64String(BinaryData)
Auth = "Basic " & Auth
HTTPMsgHdr.Headers("AUTHORIZATION") = Auth
End If
Else
Throw New Exception("Received unexpected empty object HTTPMsgHdr from request properties. " _
& "This error occurred in class ""MessageInspector"" and function ""BeforeSendRequest."" ")
End If
End If
Catch ex As Exception
mErrText = "Error caught in BeforeSendRequest function of MessageInspector class: Action = " _
& Action & "; Message = " & ex.Message & " "
If Not ex.InnerException Is Nothing Then
mErrText &= "Additional Information: " & ex.InnerException.ToString & " "
End If
Throw New Exception(mErrText)
End Try
Return Convert.DBNull
End Function
End Class
Finally, here is the config file:
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<configSections>
</configSections>
<appSettings>
<!-- Client Credentials -->
<add key="CliCredUserName" value="xxxxxxx"/>
<add key="CliCredPassword" value="xxxxxxx"/>
<!-- Login Object Fields -->
<add key="ClinicID" value="xxxxx"/>
<add key="CorporateID" value="x"/>
<add key="Password" value="xxxxx"/>
<add key="UserName" value="xxxx"/>
</appSettings>
<system.serviceModel>
<diagnostics>
<messageLogging logEntireMessage="false" logMalformedMessages="false"
logMessagesAtServiceLevel="false" logMessagesAtTransportLevel="false" />
</diagnostics>
<bindings>
<basicHttpBinding>
<binding name="ZoasisGroupServicesPort" closeTimeout="00:01:00"
openTimeout="00:01:00" receiveTimeout="00:10:00" sendTimeout="00:01:00"
allowCookies="false" bypassProxyOnLocal="false" hostNameComparisonMode="StrongWildcard"
maxBufferSize="65536" maxBufferPoolSize="524288" maxReceivedMessageSize="65536"
messageEncoding="Text" textEncoding="utf-8" transferMode="Buffered"
useDefaultWebProxy="true">
<readerQuotas maxDepth="32" maxStringContentLength="118192" maxArrayLength="16384"
maxBytesPerRead="4096" maxNameTableCharCount="16384" />
<security mode="Transport">
<transport clientCredentialType="None" proxyCredentialType="None"
realm="" />
<message clientCredentialType="UserName" algorithmSuite="Default" />
</security>
</binding>
</basicHttpBinding>
</bindings>
<client>
<endpoint address="xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
binding="basicHttpBinding" bindingConfiguration="ZoasisGroupServicesPort"
contract="AntechServiceReference.ZoasisGroupServicesPort" name="ZoasisGroupServicesPort" />
</client>
</system.serviceModel>
<system.net>
<!-- Important: The following setting strips off the "HTTP/1.1 100 Continue" banner from incoming
messages. Unless this is done, incoming XML messages are not recognized as XML. -->
<settings>
<servicePointManager expect100Continue="false"/>
</settings>
</system.net>
</configuration>
As I mentioned earlier, this is a functioning WCF client that successfully calls the service and downloads data - but only when run within Visual Studio, which is the part I don't understand.

This is what I had to do to fix this problem. In the BeforeSendRequest function of the MessageInspector class, I had to add the code indicated below (i.e. the lines between the
rows of exclamation points - !!!!!!)
Action = "Checking HTTP headers. "
If request.Properties.TryGetValue(HttpRequestMessageProperty.Name, objHTTPRequestMsg) Then
Action = "Changing existing HTTP header. "
HTTPMsgHdr = CType(objHTTPRequestMsg, HttpRequestMessageProperty)
If Not HTTPMsgHdr Is Nothing Then
If String.IsNullOrEmpty(HTTPMsgHdr.Headers("AUTHORIZATION")) Then
Auth = mUserName & ":" & mPassword
ReDim BinaryData(Auth.Length)
BinaryData = Encoding.UTF8.GetBytes(Auth)
Auth = Convert.ToBase64String(BinaryData)
Auth = "Basic " & Auth
HTTPMsgHdr.Headers("AUTHORIZATION") = Auth
End If
Else
Throw New Exception("Received unexpected empty object HTTPMsgHdr from request properties. " _
& "This error occurred in class ""MessageInspector"" and function ""BeforeSendRequest."" ")
End If
' !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
' Added this section
Else
Action = "Creating new HTTP header. "
HTTPMsgHdr = New HttpRequestMessageProperty
If String.IsNullOrEmpty(HTTPMsgHdr.Headers("AUTHORIZATION")) Then
Auth = mUserName & ":" & mPassword
ReDim BinaryData(Auth.Length)
BinaryData = Encoding.UTF8.GetBytes(Auth)
Auth = Convert.ToBase64String(BinaryData)
Auth = "Basic " & Auth
HTTPMsgHdr.Headers("AUTHORIZATION") = Auth
End If
request.Properties.Add(HttpRequestMessageProperty.Name, HTTPMsgHdr)
' End of Added section
' !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
End If
For some reason still not clear to me, when I run the application as an executable, the "HttpRequestMessageProperty.Name" property does not exist in the request.properties that are passed into the "BeforeSendRequest" function. I have to explicitly create it - unlike when I run the application in Debug mode in Visual Studio. (Thanks to Mike Parkhill, for suggesting that the "If" conditions might not be executing as I expected. It turns out I needed an extra ELSE clause as shown above.)

Related

How to use ComboBox to extract information form XML file?

I have a XML file that contain a lot of information.
So, I would like to create a macro in VBA Excel that allow me to filter the information based on the accountID (available form an Userform - ComboBox)
It's the first time I work with XML and userform.
I've tried to adjust multiple code that I found on the net, but i understand better this one so I would like to continue with something similar (if possible):
Private Sub ComboBox1_Click()
Dim wks As Worksheet
Set wks = Sheet2
' Load the XML document
Dim XDoc As Object, root As Object
Set XDoc = CreateObject("MSXML2.DOMDocument")
XDoc.async = False: XDoc.validateOnParse = False
XDoc.Load ("C:\Users\isabelle\Google Drive\IB API Integration\Flexqueries\FlexDay.xml")
Dim singleNode As Object
Set singleNode = XDoc.SelectSingleNode("//FlexQueryResponse/FlexStatements/FlexStatement[#accountId='U2396623']")
End Sub
The accountId 'U2396623' is only an example. This will correspond to the entry in the combobox.
My two problems are :
First, I don't know how to link the entry in the combobox to the code (based on the accountID)
Second, code above does not work. I think the path (XDoc.SelectSingleNode) is not the right one... So, I tied several combination and it never worked. Or maybe it's the output that does not work correctly (The output should be in the Sheet2 )
My XML file looks like it :
<FlexQueryResponse queryName="Sample_1" type="AF">
<FlexStatements count="10">
<FlexStatement accountId="" fromDate="2019-04-22" toDate="2019-05-21" period="Last30CalendarDays" whenGenerated="2019-05-22;13:49:30">
<AccountInformation accountId="" acctAlias="" currency="CAD" accountType="Advisor Client" dateOpened="2018-02-08" dateFunded="2018-03-01" dateClosed="" street="" street2="" city="" state="" country="" postalCode="" primaryEmail="" />
<ChangeInNAV accountId="" acctAlias="" startingValue="" endingValue="" realized="0" changeInUnrealized="0" depositsWithdrawals="0" twr="" mtm="" dividends="" changeInDividendAccruals="" interest="" changeInInterestAccruals="" advisorFees="" clientFees="0" otherFees="0" />
<CashReport>
<CashReportCurrency accountId="" acctAlias="" clientFees="0" commissions="" deposits="0" withdrawals="0" accountTransfers="0" dividends="" advisorFees="" otherFees="0" currency="BASE_SUMMARY" startingCash="" endingCash="" endingSettledCash="" />
/>
</CashReport>
<OpenPositions>
<OpenPosition accountId="" acctAlias="" symbol="" position="" costBasisPrice="" strike="" expiry="" putCall="" positionValue="" percentOfNAV="" fifoPnlUnrealized="" currency="CAD" fxRateToBase="1" assetCategory="STK" description="BOMBARDIER INC PFD SER 2" securityID="CA0977515075" cusip="" isin="CA0977515075" />
</OpenPositions>
<FxPositions>
<FxPosition accountId="" fxCurrency="CAD" quantity="" costPrice="" unrealizedPL="0" />
</FxPositions>
<OptionEAE>
<OptionEAE accountId="" acctAlias="" currency="USD" assetCategory="" symbol="" description="" securityID="" cusip="" isin="" listingExchange="" underlyingConid="" underlyingSymbol="FCAU" underlyingSecurityID="NL0010877643" underlyingListingExchange="NYSE" issuer="" strike="16" expiry="2019-05-03" putCall="C" date="2019-04-22" transactionType="Assignment" quantity="2" tradePrice="0.0000" markPrice="0.3500" realizedPnl="0.00" />
</OptionEAE>
<PendingExcercises />
<ClientFees />
<OpenDividendAccruals>
<OpenDividendAccrual accountId="" acctAlias="" currency="" assetCategory="" symbol="" description="" securityID="" cusip="" isin="" exDate="2019-05-03" payDate="2019-06-24" quantity="400" grossRate="0.23" grossAmount="92" netAmount="92" />
</OpenDividendAccruals>
</FlexStatement>
</FlexStatements>
</FlexQueryResponse>
If you have any question don't hesitate !
Thank you for your help
Consider several adjustments:
Use the AfterUpdate trigger event of combobox.
Private Sub ComboBox1_AfterUpdate()
...
End Sub
Simply concatenate the combobox value Me.ComboBox1 to XPath expression.
XDoc.SelectSingleNode("/FlexQueryResponse/FlexStatements/FlexStatement[#accountId='" & Me.ComboBox1 & "']")
Select the needed attribute value or loop through multiple by node for extraction to sheet.
Private Sub ComboBox1_AfterUpdate()
' Load the XML document
Dim XDoc As Object, root As Object, singleNode As Object
Dim i As Integer
Set XDoc = CreateObject("MSXML2.DOMDocument")
XDoc.async = False: XDoc.validateOnParse = False
XDoc.Load ("C:\Users\isabelle\Google Drive\IB API Integration\Flexqueries\FlexDay.xml")
' OUTPUT ONE ATTRIBUTE OF NODE
Set singleNode = XDoc.SelectSingleNode("/FlexQueryResponse/FlexStatements/FlexStatement[#accountId='" & Me.ComboBox1 & "']")
ThisWorkbook.Worksheets("Sheet1").Range("A1") = singleNode.Attributes.getNamedItem("accountId").Text
' OUTPUT ALL ATTRIBUTES OF NODE
For i = 0 To singleNode.Attributes.Length - 1
ThisWorkbook.Worksheets("Sheet1").Range("A" & i + 1) = singleNode.Attributes(i).Name
ThisWorkbook.Worksheets("Sheet1").Range("B" & i + 1) = singleNode.Attributes(i).Text
Next i
Set XDoc = Nothing
End Sub

Using Registration Free COM in Excel 2010

I have been struggling to use Registration Free COM to make .NET calls from Excel 2010. Essentially I wanted to be able to call some .NET API functions in an Excel macro. I created a wrapper API in .NET, and exposed that wrapper as a COM-callable library. The code for that is as follows:
Imports System
Imports System.Collections.Generic
Imports System.Runtime.InteropServices
Imports NXOpen
Imports NXOpen.UF
Imports NXOpen.Assemblies
Namespace ExcelNXInterface
<Guid("bbe0089d-a732-4743-922b-180b30006fa4"), _
InterfaceType(ComInterfaceType.InterfaceIsIDispatch)> _
Public Interface _ExcelNXInterface
<DispId(1)> Sub HighlightCompTag(ByRef tag As Long)
<DispId(2)> Sub Echo(ByVal output As String)
End Interface
<ComVisible(True)>
<Guid("1376DE24-CC2D-46cb-8BF0-887A9CAF3014"), _
ClassInterface(ClassInterfaceType.None), _
ProgId("ExcelNXInterface.ExcelNXInterface")> Public Class ExcelNXInterface
Implements _ExcelNXInterface
Public _ExcelNXInterface
Dim theSession As Session = Session.GetSession()
Dim ufs As UFSession = UFSession.GetUFSession()
Public Sub HighlightCompTag(ByRef tag As Long) Implements _ExcelNXInterface.HighlightCompTag
Try
ufs.Disp.SetHighlight(tag, 1)
Echo("Component(s) Highlighted...")
Catch e As NXException
Echo("NX Exception is: {0} " + e.Message)
Catch e As Exception
Echo("Exception is: {0} " & e.Message)
Echo("DONE!" & vbLf)
End Try
End Sub
Sub Echo(ByVal output As String) Implements _ExcelNXInterface.Echo
theSession.ListingWindow.Open()
theSession.ListingWindow.WriteLine(output)
theSession.LogFile.WriteLine(output)
End Sub
End Class
End Namespace
The manifest to use Registration Free COM is as follows:
<?xml version="1.0" encoding="utf-8"?>
<asmv1:assembly
manifestVersion="1.0"
xmlns="urn:schemas-microsoft-com:asm.v1"
xmlns:asmv1="urn:schemas-microsoft-com:asm.v1"
xmlns:asmv2="urn:schemas-microsoft-com:asm.v2"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<assemblyIdentity
type="win32"
version="1.0.0.0"
name="ExcelNXInterface"/>
<clrClass
clsid="{bbe0089d-a732-4743-922b-180b30006fa4}"
progid="ExcelNXInterface.ExcelNXInterface"
threadingModel="Both"
name="ExcelNXInterface.ExcelNXInterface">
</clrClass>
<file name = "ExcelNXInterface.dll"></file>
</asmv1:assembly>
To call the wrapped .NET functions in Excel I used the following VBA code:
Sub ExNX()
Dim actCtx As Object
Set actCtx = CreateObject("Microsoft.Windows.ActCtx")
actCtx.Manifest = ThisWorkbook.Path & "\ExcelNXInterface.dll.manifest"
Dim myNX As Object
Set myNX = actCtx.CreateObject("ExcelNXInterface.ExcelNXInterface")
End Sub
However, the problem is that when the last line of code is called I get a Run time error 2147024894 (80070002) Method 'CreatObject' of object IActCtx' failed. I honestly don't know what it is that I am doing wrong, or even if this is possible. Any help or suggestions are greatly appreciated.
It should be noted that the DLL, manifest and the workbook are all in the same directory. Also it should be noted that VSTO, executable files and/or installations are not options for me. That is why I am trying to stick to using Excel.

Implementing Single Sign-On from .NET Application to SAP System, using SAP.NET Connector 3.0

We are trying to use SAP.NET NCo 3.0 to implement single sign on from .net application to SAP System. In the configuration set up method we are fetching user name and password along with other configuration information from configuration file.
E.g.
RfcConfigParameters rfcConfig = new RfcConfigParameters();
rfcConfig.Add(RfcConfigParameters.User, ConfigurationSettings.AppSettings["SAP_USRNAME"]);
rfcConfig.Add(RfcConfigParameters.Password, ConfigurationSettings.AppSettings["SAP_PWD"]);
rfcConfig.Add(RfcConfigParameters.Client, ConfigurationSettings.AppSettings["SAP_CLIENT"]);
We are looking for a way that we can implement SSO with windows authentication where will ne NO need to pass user id and password explicitly. We also have SNC configuration and other required file available with us.
Any relevant code snippet or pointer addressing this will be of great help.
Thanks in advance
You need to make a http request to the SAP portal from the client. This will give you the SAPSSO2 token (parse it out of the http headers you receive, sample in VB):
Public Function GetSAPSSOTicket(sPortalURL As String, ByRef Ticket As String, ByRef ErrorMsg As String) As Boolean
Dim offset As Long
GetSSOTicket = False
ErrorMsg = ""
Ticket = ""
Const MYSAPSSO2 As String = "MYSAPSSO2="
On Error GoTo Err1
'contact the sap portal
Dim req As New WinHttp.WinHttpRequest
req.Open "GET", sPortalURL, False
req.SetAutoLogonPolicy AutoLogonPolicy_Always
req.Send
Dim S As String
S = req.GetAllResponseHeaders()
'parse the ticket out of the response
offset = InStr(1, S, MYSAPSSO2, vbTextCompare)
If offset <= 0 Then
ErrorMsg = "The Portal Server returned an empty ticket. Authentication failed."
GoSub Cleanup
Exit Function
End If
S = Mid(S, offset + Len(MYSAPSSO2))
offset = InStr(1, S, ";")
S = Left(S, offset - 1)
Ticket = S
'complete
On Error GoTo 0
'success
GoSub Cleanup
GetSSOTicket = True
Exit Function
Cleanup:
Set req = Nothing
Return
Err1:
'some error
GoSub Cleanup
ErrorMsg = Err.Description
End Function
Next, transport this token to your SAP.NET connector code where you make your destination and connection, and assign the value you obtained to the destination's SAPSSO2 property (sample in c#):
var destX = new SAP.Connector.Destination();
destX.Type = "3"; /* meaning R/3 */
destX.AppServerHost = "hostname";
destX.Client = (short)99; /* your client number here */
destX.SystemNumber = (short)42; /* your system number here */
/* single sign-on token passed in from SAPSSO2 header value in sapCookie parameter */
destX.MySAP_SSO2 = System.Web.HttpUtility.UrlDecode(sapCookie, Encoding.UTF8);
destX.Language = "DE";
destX.MsgServerHost = "message server (if needed, otherwise blank)";
destX.LogonGroup = "group name (or blank)";
destX.AbapDebug = false;
destX.Trace = true;
sap.Connection = new SAP.Connector.SAPConnection(destX);
sap.Connection.Open();
We have this code in production since 2004 and it survived many releases, up to and including SAP HANA with Unicode.

Unable to test the Transient Fault Handling Application Block for Azure

I'm having trouble performing a simple test of the Transient Fault Handling Application Block, so I must be missing something silly.
Based on the information in http://msdn.microsoft.com/en-us/library/hh680927(v=pandp.50).aspx and
http://azuretable.blogspot.com/2012/08/unit-testing-transient-errors-in-azure.html I have created the mock class below:
Public Class DBUtils_TestRetryPolicy
' see http://msdn.microsoft.com/en-us/library/hh680927(v=pandp.50).aspx
' see http://azuretable.blogspot.com/2012/08/unit-testing-transient-errors-in-azure.html
Public Class TransientMock
Implements ITransientErrorDetectionStrategy
Public Function IsTransient(ex As System.Exception) As Boolean Implements Microsoft.Practices.TransientFaultHandling.ITransientErrorDetectionStrategy.IsTransient
Return (True)
End Function
End Class
Private Shared mockExceptionCounter As Integer = 0
Private Shared retryLog As String = ""
Private Sub ThrowException()
mockExceptionCounter += 1
Throw New Exception("This is as mock exception to test retries")
End Sub
Public Function TestRetryLogic() As String
Dim theRetryStrategy = New Incremental(6, TimeSpan.FromSeconds(1), TimeSpan.FromSeconds(2)) ' should result in retries at 0, 1, 3, 5, 7, 9 seconds (25 seconds total)
theRetryStrategy.FastFirstRetry = True
Dim theRetryPolicy = New RetryPolicy(Of TransientMock)(theRetryStrategy)
AddHandler theRetryPolicy.Retrying, AddressOf OnMockConnectionRetry
mockExceptionCounter = 0
retryLog = ""
Try
theRetryPolicy.ExecuteAction(New System.Action(AddressOf ThrowException))
Catch ex As Exception
' here we should have the last exception thrown after all the retries
Dim a As Integer = 231234234
End Try
Return (retryLog)
End Function
Private Sub OnMockConnectionRetry(sender As Object, e As RetryingEventArgs)
retryLog += DateTime.UtcNow.ToString + " [" + mockExceptionCounter.ToString() + "] -> CurrentRetryCount [" + Cstr(e.CurrentRetryCount) + "]" + "Delay (ms) [" + CStr(e.Delay.TotalMilliseconds) + "]" + "LastException [" + e.LastException.Message + "]"
End Sub
End Class
All I do in my code is to instantiate this class and call TestRetryLogic().
I ran the Visual Studio debugger expecting to see a few retries, but what I get is a popup from Visual Studio saying "Exception was unhandled by user code". This happens as soon as I throw inside the method ThrowException(). Of course no retries seem to be happening.
What am I missing?
EDIT: I was failing to cast to string inside OnMockConnectionRetry, so I assume I was throwing an exception inside an (already "running") exception-handling block. By using the tip from Petar I was able to see (and fix) this little problem and now the retries are working as expected.
You need to turn off Common Language Runtime Exceptions by un-checking the User-unhandled option to not get interrupted by VS. This is located under Debug/Exceptions menu.

Protecting an email password when using NLog

When using NLog as a logging instrument we can easily send messages via email, for example this is the example configuration for using Gmail as a smtp server:
<targets>
<target name="gmail" type="Mail"
smtpServer="smtp.gmail.com"
smtpPort="587"
smtpAuthentication="Basic"
smtpUsername="user#gmail.com"
smtpPassword="password"
enableSsl="true"
from="emailaddress#gmail.com"
to="recipient#example.com"
cc="alice#example.com;bob#example.com;charlie#example.com"
/>
</targets>
<rules>
<logger name="*" minlevel="Debug" writeTo="gmail" />
</rules>
It works like a charm.
But in the above example the password is put in plain text in a configuration file.
Is there a way to protect it somehow?
Yes, you can move the NLog.config (If you have it in this file) to your app.config and then encrypt you app.config.
You can see how to encrypt the app.config here.
You could configure the logger inside code. There, you can even hide your password from anyone with a hex-editor! Plus, nobody gets any chance to mess with your config file.
Public Class MyEmailLoggerClass
Public Sub New()
SetNlogConfiguration()
End Sub
Private Sub SetNlogConfiguration()
Private MyNLogConfiguration As New LoggingConfiguration()
Dim myemailtarget As New MailTarget()
Dim MailRule As New LoggingRule("myemail", LogLevel.Info, myemailtarget)
With myemailtarget
.SmtpServer = "mail.724hosting.com"
.SmtpAuthentication = SmtpAuthenticationMode.Basic
.SmtpUserName = "myemail" & "#" & "somewhere.com"
.SmtpPassword = "my" & "pass" & "word"
.SmtpPort = 587
'.Encoding = System.Text.Encoding.UTF8
.EnableSsl = true
.AddNewLines = True
.Html = True
.Layout = "${message}"
'message options
.To = "sometech" & "#" & "somewhere.com"
.cc = "bob#somewhere.com,harry#somewhereelse.com"
.From = "myapplication#here.com"
.Header = "<h2 style='color:red'>Message from myapplication!</h2>"
.Subject = "Report from " & myapplicationname & " on someone's computer" & ${date:F}"
.Body = "${message}"
End With
LogManager.Configuration = myNlogConfig
End Sub
End Class
To use it, put this in the sub where you want to send email:
Public MainLogger As New MyEmailLoggerClass
Dim Logger = LogManager.GetLogger("myemail")
Logger.Info("Hi, this is a message from my application")

Resources