Web Services Connectivity and Programming Guide
Transcription
Web Services Connectivity and Programming Guide
finPOWER Connect 2 Web Services Connectivity and Programming Guide Version 1.12 30th March 2015 N:\DATA\Development\finPOWER Connect\Version 2\Web Services\finPOWER Connect Web Services Connectivity and Programming Guide.doc Table of Contents Disclaimer.................................................................................................................... 5 Version History ............................................................................................................. 7 Introduction ................................................................................................................. 8 Limitations ................................................................................................................ 8 System Requirements and Prerequisites .......................................................................... 9 Help Resources ........................................................................................................... 10 Web Services ............................................................................................................. 11 Connectivity and Security ............................................................................................ 12 Encryption .............................................................................................................. 12 Authentication ......................................................................................................... 12 Setting up a Test Environment ..................................................................................... 13 Setup and Connection .............................................................................................. 13 Web Administrator ................................................................................................ 15 User .................................................................................................................... 16 Client................................................................................................................... 17 Signing In ............................................................................................................ 18 Using the Web Services Test Form ............................................................................. 19 Programming Languages ............................................................................................. 21 Microsoft .NET ......................................................................................................... 21 Parsing using the XmlDocument .............................................................................. 21 Deserialisation into a .NET Object............................................................................ 21 Authenticating (Signing In) .......................................................................................... 23 Web Roles ............................................................................................................... 23 Web Subscribers ...................................................................................................... 23 The Authentication Process ....................................................................................... 23 hashSalt .............................................................................................................. 23 hash .................................................................................................................... 24 Authentication Code Sample ................................................................................... 24 Authentication Response ........................................................................................ 27 Supplying the Session Token with a Request ............................................................ 28 When to Re-Authenticate ....................................................................................... 28 Dates ........................................................................................................................ 30 Custom Web Services .................................................................................................. 31 Overview ................................................................................................................ 31 Creating a Custom Web Service Script ........................................................................ 32 Calling the Custom Web Service ................................................................................ 33 Accepting Parameters ............................................................................................... 34 Controlling the Response .......................................................................................... 36 HTML Response..................................................................................................... 36 JSON Response ..................................................................................................... 36 Page 2 of 72 Text Response ...................................................................................................... 36 XML Response ...................................................................................................... 36 PDF Response ....................................................................................................... 36 Accepting Posted Data .............................................................................................. 38 Serialising the Response ........................................................................................... 40 Nullable Types ...................................................................................................... 41 Dates .................................................................................................................. 43 Enums ................................................................................................................. 45 Using Attributes to Tweak Serialisation .................................................................... 46 Preventing Properties From Being Serialised ............................................................. 47 Serialising Collections ............................................................................................ 49 Deserialising the Request .......................................................................................... 51 Testing Posted XML ............................................................................................... 53 Enums ................................................................................................................. 54 Using Attributes to Tweak Deserialisation ................................................................. 55 Deserialising Collections ......................................................................................... 56 Troubleshooting Deserialisation Issues ..................................................................... 58 Parsing Posted XML Request ...................................................................................... 59 Serialising and Deserialising XML .................................................................................. 61 Serialisation ............................................................................................................ 61 Visual Basic .......................................................................................................... 62 C# ...................................................................................................................... 63 Deserialisation ......................................................................................................... 64 Visual Basic .......................................................................................................... 65 C# ...................................................................................................................... 66 PDF Documents .......................................................................................................... 67 Returning a PDF Document from a Custom Web Service ............................................... 67 Returning a Response Containing a PDF Document Data from a Custom Web Service ....... 68 Formatting PDF Documents ....................................................................................... 69 Visual Basic Code Examples ......................................................................................... 70 Troubleshooting .......................................................................................................... 72 Timeout when Authenticating Client ........................................................................... 72 Misconfigured Address Database ............................................................................. 72 Page 3 of 72 Page 4 of 72 Disclaimer This document contains information that may be subject to change at any stage. All code examples are provided "as is". Copyright Intersoft Systems Ltd, 2014. Page 5 of 72 Page 6 of 72 Version History Date Version Name Changes 07/03/2014 1.00 PH Created. 17/03/2014 1.01 PH Clarified that Web Services cannot currently be used for accessing external resources. 20/03/2014 1.02 PH Added serialisation/ deserialisation section and samples. 09/04/2014 1.03 PH Added dates section. 02/05/2014 1.04 PH Updated information regarding session tokens and when to re-authenticate. 06/05/2014 1.05 PH Clarified section detailing session token in Authorization header. 06/06/2014 1.06 PH Added troubleshooting guide for address database issues and PDF sections. 21/07/2014 1.07 PH Updated ExecuteRequest sample. 21/07/2014 1.08 PH Support for external Web Services detailed in Limitations section. 12/08/2014 1.09 PH Custom Web Services section enhanced. 13/08/2014 1.10 PH Custom Web Service Enum deserialisation. 01/10/2014 1.11 PH Custom Web Service section used to note new "Execute (Parameters)" option from Test Web Service form. 30/03/2015 1.12 PH Added link to Microsoft article in Introduction, Web Services section. Page 7 of 72 Introduction This document is intended for software developers who would like to connect to the finPOWER Connect Web Services over HTTP. This document provides all of the necessary information needed to connect to and perform requests against the Web Services. For information on installing and configuring the finPOWER Connect Web Services on a Web Server, see the finPOWER Connect 2 Web Services Installation and Configuration document. Limitations Where access to a resource external to the finPOWER Connect database is required and a builtin Web Service does not exist to achieve this, it should be assumed that such access cannot currently be achieved and therefore any Custom Web Services or Scripts that run within finPOWER Connect as part of a Web Service call cannot access these resources. This includes the following: Document Manager o Accessing of the Document Manager folder structure is not possible in the usual manner from a Web Service. Any form of document publishing, E.g., using a Word VBA template to create a document. o Microsoft Office should never be run on a Web Server and therefore no form of document publishing is available from a Web Service. Access to external Web Services, E.g.: o PPSR G2B o Credit Bureau (Centrix, Veda, DecisionLogic etc) o MotorWeb WARNING: Any Custom Web Services or Scripts that attempt to access external resources cannot be supported and cannot be guaranteed to work in future releases of finPOWER Connect and the finPOWER Connect Web Services. Unless listed in the table below, assume the external resource is not supported. The following external Web Services are supported. The Web Services version at which support was added is detailed below: Service Version Details Credit Sense 2.01.01.00 Comprehensive Web Service support. Veda (Australia) 2.01.01.00 Limited Web Services since it is anticipated most functionality will be performed from Custom Web Services. Page 8 of 72 System Requirements and Prerequisites Since the finPOWER Connect Web Services use HTTP, the only programming requirement is a development environment that allows HTTP and secure HTTP (HTTPS) requests to be made. o The application being developed can use any programming language and exist on any platform providing the above is supported. A connection to a Web Server running the finPOWER Connect Web Services is required. o This Web Server might be: Accessible over the Internet. Accessible over a private LAN. Running on the development PC (for testing purposes). The finPOWER Connect 2 Web Services Installation and Configuration document details installation of the Web Services for testing purposes. From a development point-of-view, a solid knowledge of the following is recommended: o HTTP connectivity. o XML and/ or JSON. Most code samples are currently limited to Visual Basic (VB.NET). Page 9 of 72 Help Resources The finPOWER Connect Web Services have online help detailing all Web Services. This help is available from: The finPOWER Connect Web Services Web Server via the API reference link on the Sign In page: From within the finPOWER Connect Windows User Interface via the Test Web Services form (Tools, Web, Test Web Services). o The help is available by selecting a Web Service on the Web Services tab, providing a valid URL has been entered on the Connect page. This help is constantly evolving and is updated for every new Web Service that is added. Page 10 of 72 Web Services All Web Services are based on the Microsoft Web API framework meaning: o They use HTTP/ HTTPS as a transport protocol. o They do not use SOAP and therefore no WSDL is available. XML (or JSON) can be parsed manually or, if using a language that supports it (e.g., VB.NET), deserialised into a simple object. o They use a RESTful, RPC based approach meaning: Most services are fully URL-based, E.g., to retrieve a list of Accounts for a finPOWER Connect Client, you would simply request a URL such as: /Api/Client/GetAccounts?clientId=C10000&includeQuote=true o Unless otherwise specified, both XML and JSON are supported. Generally, only the HTTP response will contain XML/ JSON data but services that require more than just simple URL parameters can POST either XML or JSON. The finPOWER Connect Web Services consist of many individual services. o These services are organised into 'Controllers' which is simply a way of grouping related services, e.g., the above URL example calls the GetAccounts service which is located in the Client controller. NOTE: The Web Services help and the Test Web Services form within finPOWER Connect organise their table of contents with a higher level of grouping for readability, e.g., both Client and ClientWebMail controllers are grouped within a Client folder. The following are useful articles for understanding Microsoft's Web API and REST services: http://blogs.msdn.com/b/martinkearn/archive/2015/01/05/introduction-to-rest-and-net-webapi.aspx Page 11 of 72 Connectivity and Security Connectivity to the Web Services is via HTTP/ HTTPS. o HTTP is fine for testing but secure HTTP (HTTPS) MUST be used in a production environment. o The HTTP link to Web Services may be over a private network or the Internet (public) or, to an installation of the Web Services running on the same PC (usually for development purposes only, using localhost). Encryption All communication (in a production environment) takes place over a secure, encrypted HTTP channel using SSL/ TLS. o This is enabled by a certificate installed on the Web Server hosting the Web Services and is outside of the scope of this document. Authentication Most Web Services require authentication. Any application wishing to access the Web Services must have a Web Subscriber record defined in the finPOWER Connect database. o This record has an Id and a Secret key, both of which are required for authentication. Authenticated services must be passed a special Session Token as an HTTP header. This is returned from the initial authentication request. o The Session Token is valid for up to 24 hours. Page 12 of 72 Setting up a Test Environment When programming against the finPOWER Connect Web Services, it is useful to be able to test the Web Services without having to write any code. finPOWER Connect provides a Test Web Services form for this purpose. The following steps are required to set up a test environment. NOTE: This assumes you can connect to a Web Server running the finPOWER Connect Web Services. Installation and configuration of the Web Services is detailed in the finPOWER Connect 2 Web Services Installation and Configuration document. Setup and Connection Install the latest version of finPOWER Connect. Open finPOWER Connect. o If available, open the finPOWER Connect database to which the Web Services connect. This is not necessary but does make the connection and testing process easier, E.g., if you call a Web Service to add an Account, you can then view the Account directly from within finPOWER Connect. o If unavailable, open any finPOWER Connect database, E.g., the demonstration database. Ensure you are logged in as an administrator User. From the Tools menu, select Web, Test Web Services. On the Connect page, enter the URL of the Web Services to access, E.g. NOTE: The Web Services URL will always end with /Api/. Click the Ping button to test that the Web Services are available. All going well, you should see a message like this: Page 13 of 72 o NOTE: Ping is one of the few Web Services that do not require authentication. Now you can connect to the Web Services by specifying details of the user to connect as. The User Type determines how you connect. o Note that connecting as a finPOWER Connect User or a Client requires the Id and Secret Key of a Web Subscriber to connect as. o If you have the finPOWER Connect Web Services database loaded, you can add a new Web Subscriber record (if required) as follows: From the Tools menu, select Web, Web Subscribers. Click the Add button and enter the following: Code: TEST Name: Test Web Services Click the Save button. Close the Web Subscribers form. o If you do not have the finPOWER Connect Web Services database, you will need to contact the database administrator and ask them to set up a Web Subscriber record for you to use and to supply you with the Web Subscriber Id and Secret Key. Page 14 of 72 Web Administrator Connecting as a Web Administrator requires only the Web Administration User Id and Password. By default these are: o User Id: webadmin o Password: password NOTE: The Web Administrator role is for use in the Web Services Administration facility and would not generally be used by external applications. Page 15 of 72 User Connecting as a finPOWER Connect User requires both the User Id and Password and also the Id and Secret Key of a Web Subscriber to connect as. If you have the Web Services database open, you can select the Subscriber from the dropdown. If not, you will need to be enter the details manually in the Subscriber dropdown and Secret Key fields. Enter the credentials of a finPOWER Connect User, E.g. o User Id: admin o Password: admin NOTE: The User must have been granted Web Access. This is configured via the Web Access page on the Users form (Tools, User Security, Users). Page 16 of 72 Client Connecting as a finPOWER Connect Client requires both the Client Id and Password and also the Id and Secret Key of a Web Subscriber to connect as. If you have the Web Services database open, you can select the Subscriber from the dropdown. If not, you will need to be enter the details manually in the Subscriber dropdown and Secret Key fields. Enter the credentials of a finPOWER Connect Client, E.g. o Client Id: C10000 o Password: Password1 NOTE: The Client must have been granted Web Access. This is configured via the Web Access page on the Clients form (Client, Clients). Page 17 of 72 Signing In After entering the Web Administrator/ User/ Client details, click the Connect button to sign in. You should see a message similar to this: And a green box will appear in the top-right corner of the form to show that you have connected successfully: o If signing in was unsuccessful, you will see a message such as: Click OK and switch to the Web Services tab. Click the Response tab. The response should contain an XML error message which should help you determine why signing in failed. The following example has removed attributes on the Error tag for clarity): Page 18 of 72 Using the Web Services Test Form Now that you have successfully connected to the Web Services, you can test any Web Service or view online help as follows. Switch to the Web Services tab. Locate and select the Web Service to test in the explorer. o You can use the Quick Search box above the explorer to filter Web Services. The Help tab allows you to view the selected Web Service's help. The Request and Response pages allows you to view the information sent to and received from the Web Services. For example: o Type Client Accounts into the Quick Search box. o Select the Client, GetAccounts Web Service. o A list of parameters that this Web Service accepts is displayed along with help for the Web Service, E.g. o Enter any parameters, E.g. Client Id: C10000 Include Quote: checked o Click the Test button. o The Response tab will automatically be selected and the results of the test will be displayed, E.g. Page 19 of 72 By default, the response will be retrieved as XML. This can be changed from the Connect page by changing the Format to JSON. Page 20 of 72 Programming Languages You may develop your application in the programming language of your choice, on the platform of your choice. The majority of the code samples throughout this document however use Visual Basic (VB.NET). Most finPOWER Connect Web Services allow you to use either XML or JSON for transferring data. NOTE: No formal XML or JSON schemas are defined and the XML/ JSON responses may be extended over time hence any parsing code should assume that the response may contain additional nodes/ properties in the future. Microsoft .NET Microsoft .NET programming languages (Visual Basic and C#) allow the response returned from a Web Service to be parsed in different ways, E.g., you may wish to parse an XML response using an XmlDocument object or you could use .NET's built-in deserialisation to automatically convert the XML (or JSON) into a simple .NET object. The following are Visual Basic code examples of parsing an XML error response of: Parsing using the XmlDocument Private Sub ParseXml(xml As String) Dim Document As System.Xml.XmlDocument Dim Node1 As System.Xml.XmlNode Dim Node2 As System.Xml.XmlNode Dim Message As String Dim Code As String Dim InternalMessage As String ' Load XML Document Document = New System.Xml.XmlDocument() Document.LoadXml(xml) ' Get Root <Error> node Node1 = Document.SelectSingleNode("//Error") ' Get properties Node2 = Node1.SelectSingleNode("Message") If Node2 IsNot Nothing Then Message = Node2.InnerText Node2 = Node1.SelectSingleNode("Code") If Node2 IsNot Nothing Then Code = Node2.InnerText Node2 = Node1.SelectSingleNode("InternalMessage") If Node2 IsNot Nothing Then InternalMessage = Node2.InnerText End Sub Deserialisation into a .NET Object Private Sub ParseXml(xml As String) Dim Dim Dim Dim ErrorDetails As WSErrorDetails ms As System.IO.MemoryStream Obj As Object XmlSerializer As Serialization.XmlSerializer Page 21 of 72 ' Deserialise Try ' Create Serialiser XmlSerializer = New Serialization.XmlSerializer(GetType(WSErrorDetails)) ' Deserialise ms = New System.IO.MemoryStream(Encoding.UTF8.GetBytes(xml)) ErrorDetails = DirectCast(XmlSerializer.Deserialize(ms), WSErrorDetails) Catch ex As Exception ' Failed Finally If ms IsNot Nothing Then ms.Close(): ms.Dispose() End Try End Sub <Xml.Serialization.XmlType("Error")> Public Class WSErrorDetails Public Message As String Public Code As String Public InternalMessage As String End Class A more comprehensive example of deserialisation is given in the Deserialisation section. Page 22 of 72 Authenticating (Signing In) This section details the authentication process. Most Web Service require that a special 'Session Token' is included in an HTTP header. This Session Token is generated during the authentication process, itself a Web Service, and is valid for 24 hours. Web Roles A different authentication service is used depending on the type of user that you are signing in as. The type of user defines a 'Web Role' which determines which services can be used. The Web Roles are: User (a finPOWER Connect User) Client (a finPOWER Connect Client) WebAdmin (the Web Services Administrator) You will only ever use User or Client since WebAdmin is used internally for administering Web Services. Web Subscribers For an application to use Web Services, that application must first have a Web Subscriber record defined in finPOWER Connect. Web Subscribers are maintained in finPOWER Connect from the Tools menu, Web, Web Subscribers. Each Subscriber is given a unique Subscriber Id and also a Secret Key which are used during the authentication process. If successful, the authentication service returns a Session Token. This is tied to the Web Subscriber's Secret Key therefore, if the Web Subscriber's Secret Key is changed (via the Web Subscribers form), the Session Token will immediately become invalid. The Authentication Process When authenticating, the Subscriber's Secret Key is never sent across the network. It is used to produce a Hash which is sent with the authentication request. An example URL to authenticate a finPOWER Connect User is shown below: /Api/Authentication/AuthenticateUser?subscriberId=CC&userId=Admin&password=admin&hash=Faawwodidux5zIuG %2Fe1vR8wpCVxV0aoKijjyucKbo14%3D&hashSalt=YXBIGIMHKJ The subscriberId, userId and password parameters are self-explanatory. The hash and hashSalt are explained in the next section. hashSalt The hashSalt parameter is simply a string of 10 random letters. The following Visual Basic code sample forms a random string. Rnd = New Random(CInt(Now.Ticks Mod Int32.MaxValue)) hashSalt = "" For i = 1 To 10 hashSalt &= Chr(65 + Rnd.Next(0, 25)) Next Page 23 of 72 hash The hash parameter is an SHA256 hash produced by concatenating the Hash Salt, User Id, Password and the Web Subscriber's Secret Key. The following pseudo code shows the process of creating the hash: s = hashSalt & userId & password & secretKey hash = Sha256(s) hash = Base64Encode(hash) Authentication Code Sample The following is a Visual Basic code example of signing in as a finPOWER Connect User. It demonstrates how to create the Hash required by the authentication service and how to send a request to the Web Services. The resultant Session Token can then be used for performing authenticated requests. The ExecuteRequest and CreateHash functions are taken directly from the Web Forms examples in the /Samples folder of the Web Services application. ' Connection Constants Const WEB_SERVICES_URL As String = "http://localhost/finPOWERConnectWS2/Api/" Const WEB_SUBSCRIBER_ID As String = "CC" Const WEB_SUBSCRIBER_SECRET_KEY As String = "1F673CE613414" Const USER_ID As String = "admin" Const USER_PASSWORD As String = "admin" Public Sub Test() Dim Dim Dim Dim Dim Dim Dim Dim Dim Dim Dim Document As System.Xml.XmlDocument ErrorMessage As String Hash As String HashSalt As String Node1 As System.Xml.XmlNode Nodes As System.Xml.XmlNodeList Ok As Boolean RequestUrl As String ResponseText As String SessionToken As String StatusCode As Integer ' Assume Success Ok = True ' Initialise Document = New System.Xml.XmlDocument() ' -------------------------------------------------' Connect to Web Services as a finPOWER Connect User ' -------------------------------------------------If Ok Then ' Produce a hash (and a random hash salt) of the User Id and Password Hash = HashSha256(USER_ID & USER_PASSWORD, WEB_SUBSCRIBER_SECRET_KEY, HashSalt) ' Build Request URL RequestUrl = WEB_SERVICES_URL RequestUrl &= String.Format("Authentication/AuthenticateUser?subscriberId={0}&userId={1}&passwor d={2}&hash={3}&hashSalt={4}", Server.UrlEncode(WEB_SUBSCRIBER_ID), Server.UrlEncode(USER_ID), Server .UrlEncode(USER_PASSWORD), Server.UrlEncode(Hash), Server.UrlEncode(HashSalt)) ' Execute Request Ok = ExecuteRequest(RequestUrl, "GET", "XML", "", Nothing, "", ResponseText, StatusCode, ErrorMessage, Nothing) AndAlso StatusCode = HttpStatusCode.OK ' Parse Response If Ok Then ' Parse Response to retrieve Session Token Document.LoadXml(ResponseText) SessionToken = Document.SelectSingleNode("//SessionDetails/SessionToken").InnerText End If End If ' Error If Not Ok Then Page 24 of 72 ' If ResponseText contains <Error>, this can be parsed as an XML document End If End Sub ''' <summary> ''' Execute a Request (sending either nothing, text or binary data in the form of a Byte array) to retrieve either a text or binary (Byte array) Response. ''' </summary> ''' <param name="requestUrl"> ''' The Request URL. ''' </param> ''' <param name="httpMethod"> ''' The HTTP Method, E.g., GET or POST. ''' </param> ''' <param name="contentType"> ''' The Content Type for the HTTP Content-Type header or just 'XML' or 'JSON'. ''' </param> ''' <param name="requestText"> ''' The Request Text or a blank String if not sending any text in the body of the request or if sending binary data using the requestBytes parameter. ''' </param> ''' <param name="requestBytes"> ''' The Request Bytes or Nothing if not sending binary data to the request. ''' </param> ''' <param name="sessionToken"> ''' The Session Token or a blank String is using a unauthenticated Web Service. ''' </param> ''' <param name="responseText"> ''' The Response Text. This will be Nothing if the content type of the Response did not indicate that this was a text response, i.e., it did not begin 'text'. ''' </param> ''' <param name="responseBytes"> ''' The binary Response. This will be Nothing if the content type of the Response indicates a text response. ''' </param> ''' <param name="statusCode"> ''' The HTTP Status Code received. ''' </param> ''' <param name="errorMessage"> ''' An Error Message. This will only by populated if this function returns False. ''' </param> ''' <param name="response"> ''' The Response object. May be useful for debugging purposes. ''' </param> ''' <returns> ''' A Boolean value indicating success. ''' </returns> ''' <remarks> ''' NOTE: This is a sample only and matches the function used in the Web Services Test form within finPOWER Connect. This sample may be subject to updating at any time. ''' </remarks> Private Function ExecuteRequest(requestUrl As String, httpMethod As String, contentType As String, requestText As String, requestBytes() As Byte, sessionToken As String, ByRef responseText As String, ByRef responseBytes() As Byte, ByRef statusCode As Integer, ByRef errorMessage As String, ByRef response As System.Net.HttpWebResponse) As Boolean Dim Dim Dim Dim Dim Dim Dim Dim Buffer(1023) As Byte Bytes As Integer Encoding As System.Text.UTF8Encoding Ok As Boolean MemoryStream As System.IO.MemoryStream Request As System.Net.HttpWebRequest Stream As System.IO.Stream srText As System.IO.StreamReader ' Assume Success Ok = True ' Initialise ByRef Parameters responseText = "" responseBytes = Nothing statusCode = 0 errorMessage = "" Page 25 of 72 response = Nothing ' Initialise Select Case UCase(contentType) Case "JSON" : contentType = "application/json" Case "XML" : contentType = "text/xml" End Select ' Create Web Request Try Request = DirectCast(System.Net.WebRequest.Create(requestUrl), System.Net.HttpWebRequest) With Request ' General .Method = httpMethod .ContentType = contentType .Timeout = 20000 ' 20 Seconds ' Add authorisation header If Len(sessionToken) <> 0 Then .Headers.Add("Authorization", String.Format("AuthFinWs token=""{0}""", sessionToken)) End If ' Write Request Body If Len(requestText) = 0 AndAlso requestBytes Is Nothing Then ' None .ContentLength = 0 Else ' Text/ Binary Try ' Encode Text as UTF8 If Len(requestText) <> 0 Then .ContentType &= "; charset=UTF-8" Encoding = New System.Text.UTF8Encoding() requestBytes = Encoding.GetBytes(requestText) End If ' Content Length .ContentLength = requestBytes.Length ' Content Stream = .GetRequestStream() Stream.Write(requestBytes, 0, requestBytes.Length) Catch ex As Exception Ok = False errorMessage = ex.Message Finally If Stream IsNot Nothing Then Stream.Close() Stream.Dispose() End If End Try End If End With Catch ex As Exception Ok = False errorMessage = ex.Message End Try ' Send Request and get Response Try Response = DirectCast(Request.GetResponse(), System.Net.HttpWebResponse) Catch ex As System.Net.WebException ' Not all exceptions should be treated as errors If ex.Response Is Nothing OrElse Not (TypeOf ex.Response Is System.Net.HttpWebResponse) Then Ok = False errorMessage = ex.Message Else ' Valid Response received response = DirectCast(ex.Response, System.Net.HttpWebResponse) End If Catch ex As Exception Ok = False errorMessage = ex.Message End Try ' Get Response details If Ok AndAlso response IsNot Nothing Then ' Response (as both a Byte array and Text to cater for both situations) If response.ContentLength = 0 Then ' No Response Content responseText = "" Page 26 of 72 ReDim responseBytes(0) Else ' Get either Text or Binary Response If response.ContentType.StartsWith("text", StringComparison.OrdinalIgnoreCase) OrElse InStr(response.ContentType, "/json", CompareMethod.Text) <> 0 Then ' Text srText = New System.IO.StreamReader(response.GetResponseStream()) responseText = srText.ReadToEnd() srText.Close() Else ' Binary MemoryStream = New System.IO.MemoryStream() Stream = response.GetResponseStream() Do Bytes = Stream.Read(Buffer, 0, 1023) If Bytes > 0 Then MemoryStream.Write(Buffer, 0, Bytes) Loop While Bytes > 0 responseBytes = MemoryStream.ToArray() End If End If ' Status Code statusCode = CInt(response.StatusCode) End If Return Ok End Function Public Function HashSha256(value As String, key As String, ByRef salt As String) As String Dim Dim Dim Dim Dim HashBytes As Byte() i As Integer rnd As Random SaltValueKeyBytes As Byte() Sha256 As System.Security.Cryptography.SHA256 ' Generate Salt (could just as easily be passed in but this will generate a random one regardless) rnd = New Random(CInt(Now.Ticks Mod Int32.MaxValue)) salt = "" For i = 1 To 10 salt &= Chr(65 + rnd.Next(0, 25)) Next ' Convert Salt+Value+Key into a byte array SaltValueKeyBytes = System.Text.Encoding.UTF8.GetBytes(salt & value & key) ' Hash Sha256 = New System.Security.Cryptography.SHA256Managed() HashBytes = Sha256.ComputeHash(SaltValueKeyBytes) ' Base-64 encode Return System.Convert.ToBase64String(HashBytes) End Function Authentication Response Both the Authentication/AuthenticateClient and Authentication/AuthenticateUser services return a Session Details response, both of which include a Session Token, E.g. Page 27 of 72 Supplying the Session Token with a Request The Session Token returned when authenticating (signing in) must be supplied with all authenticated requests (i.e., just about anything other than Ping). In a typical Web application, the Session Token might be stored in Session state. The Authentication Code Sample above demonstrated issuing an authenticated request to retrieve a list of a Client's Accounts. The authenticated request included a special HTTP Authorization header, E.g.: GET http://localhost/finPOWERConnectWS2/Api/Client/GetAccounts?clientId=C10000 Content-Type: text/xml Authorization: AuthFinWs token="0fb1121adfPi6BcHo48oIgTWMo9+ZA==:rW5lqC5" The token= portion of the header specifies the Session Token (this has been truncated for clarity). When to Re-Authenticate As mentioned above, authenticating (signing in), returns a Session Token that must be sent with any request that requires authentication. The Session Token is valid for 24 hours, after which, supplying it with a request will fail. Therefore, you may wish to consider the following scenarios and solutions: Never re-authenticate o When the User (or Client) first signs in, store the Session Token in Session State. Typically, for financial applications, Session State on a Web Server is set to a short period, E.g., 20 minutes of inactivity. o Therefore, if you are sure that a User is never going to be continuously connected to your Web application for more than 24 hours, this approach is suitable. o This is the method the Client Connect sample uses. Re-authenticate a User/ Client after a set period o If a User (or Client) is likely to remain signed in to your Web application for more than 24 hours, you may wish to re-authenticate after a set period, E.g., every 12 hours. o When the User (or Client) first signs in, store the Session Token together with the authentication time and the User (or Client's) Id and Password in Session State. NOTE: If using out-of-process Session State, you may wish to encrypt this information as a precaution. o Prior to performing a Web Service request, check if the Session Token is more than 12 hours old and, if so, re-authenticate using the Id and Password stored in Session State. Update the Session Token and authentication time held in Session State. Ad-hoc access, i.e., no User or Client is signed in o If your Web application has no concept of a User (or Client) signing in, E.g., an application that provides a loan application form on a public Website, you will need to store the credentials of a finPOWER Connect User somewhere in your application. You will then need to do one of the following: Page 28 of 72 Re-authenticate prior to performing a Web Service request. This provides a new Session Token that you never need to store. NOTE: This approach may bloat the finPOWER Connect audit log. When first performing a Web Service request, authenticate and store the Session Token together with the authentication time in Application State. Prior to performing a Web Service request, check if the Session Token is more than 12 hours old and, if so, re-authenticate and update the Session Token and authentication time held in Application State. Page 29 of 72 Dates Web Service dates use the ISO 8601 standard regardless of whether the date is passed as part of a request URL or is included in an XML or JSON response. Web Services return dates in UTC (Coordinated Universal Time) regardless of how they are stored within the finPOWER Connect database or displayed in the finPOWER Connect Windows interface. WARNING: Any dates provided as parameters to Web Services will be parsed according to the time zone on the Web Server. Therefore, it is advisable to always provide dates in UTC format. Dates are covered in detail in the Dates topic of the Web Services API reference. Page 30 of 72 Custom Web Services Custom Web Services are implemented via finPOWER Connect Scripts. Although General, Summary Page (version 2) and Web Summary Page type Scripts can all be used as custom Web Services, this section will concentrate on Web Service (Web API) type Scripts. This section is a basic tutorial on how to create and use a custom Web Service. Overview Custom Web Services can be written where either there is no Web Service currently available to perform a specific task or, a totally custom task is required, E.g., creating a Quote Account using custom information entered on a Web page (as per the CustomLoanQuote1VB.aspx sample detailed in the Visual Basic Code Examples section). NOTE: Always check with Intersoft Systems before choosing to write a custom Web Service if the task you want to perform could be considered standard finPOWER Connect functionality. Page 31 of 72 Creating a Custom Web Service Script This section outlines the steps required to create a simple custom Web Service Script. Open finPOWER Connect. Open the database being used by the Web Services. From the Admin menu, select Scripts. Click the Add button. Give the Script an Code and Description, E.g.: o Code: TESTWS NOTE: By default, the Script's code is the name of the custom Web Service and should therefore contain only alphanumeric characters (this can be overridden on the Web page of the Scripts form). o Description: Custom Web Service Test Specify a Script Type of Web Service (Web API). On the Script Code page, click the Paste template Script code button. Click the Save button to save the Script. Page 32 of 72 Calling the Custom Web Service The custom Web Service created above can now be called just like any other Web Service using the special Custom controller, E.g.: http://localhost/finPOWERConnectWS2/Api/Custom/TESTWS In this case, a Script named TESTWS will be called or, if a Script has a Web Service Name of 'TESTWS' defined on the Web page of the Scripts form in finPOWER Connect, this Script will be called (i.e., the Script's code is used by default but can be overridden by specifying a Web Service Name). By default, you must have already authenticated and therefore include the Authorization HTTP header as detailed in The Authentication Process section. WARNING: The Web page of the Scripts form has a checkbox to 'Allow Anonymous access'. Use this option with extreme caution since it allows your custom Web Service to be called without having first authenticated. You can test your custom Web Service from the Test Web Services form in finPOWER Connect as follows: Connect to the Web Services from the Connect page as detailed in the Setting up a Test Environment section. Switch to the Web Services page. Select the Custom, ExecuteGet node in the Web Services explorer. Enter the Id of your Script, E.g., TESTWS Click the Test button. o You should see a Response of: Switch to the Connect page and change the Format to JSON. Switch to the Web Services page and click the Test button again. o You should now see a Response of: NOTE: Custom Web Services can be called using either the 'GET' or 'POST' HTTP methods via the ExecuteGet and ExecutePost nodes in the Web Services explorer. GET-based Web Services receive all of their parameters from the URL; POST-based services can receive parameters from the URL and also from the 'Posted' RequestText, E.g., the RequestText might be XML formatted Client information. Page 33 of 72 Accepting Parameters We will now update the custom Web Service to accept parameters. The Script will be updated to accept a ClientId parameter and will return the Client's name together with a list of all their aliases (Akas). Update your custom Web Service Script code to the following: Public Function Main(request As finwsHttpRequest) As finwsHttpResponse Dim ErrorCode As String Dim ErrorStatusCode As HttpStatusCode Dim Ok As Boolean Dim Dim Dim Dim Client As finClient ClientAka As finClientAka ClientDetails As clsClientDetails ClientId As String ' Assume Success Ok = True ' Initialise ErrorStatusCode = HttpStatusCode.BadRequest ' Get Parameters ClientId = request.Parameters.GetString("ClientId") ' Load Client Client = finBL.CreateClient() If Client.Load(ClientId) Then ' Create and update Client Details ClientDetails = New clsClientDetails() ClientDetails.Name = Client.Name ' List AKAs For Each ClientAka In Client.Akas ClientDetails.Akas.Add(ClientAka.NameFull) Next Else Ok = False End If ' Response If Ok Then Return request.CreateResponse(HttpStatusCode.OK, ClientDetails) End If ' Error If Not Ok Then Return request.CreateErrorResponse(ErrorStatusCode, "Failed to get Client name details.", ErrorCode, finBL.Error.Message(True, True)) End If End Function <System.Xml.Serialization.XmlType("ClientDetails")> Public Class clsClientDetails Public Name As String Public Akas As New List(Of String) End Class We will now test the updated service using the Test Web Service form. Select the Custom, Execute (GET) node in the Web Services explorer. Enter the Id of your Script, E.g., TESTWS In the Parameters field, enter clientId=C10000. o Parameters must be URL encoded and separated by a '&', E.g., if you had two parameters, you would specify them as param1=value1¶m2=value2 Click the Test button. Page 34 of 72 o You should see a Response similar to: Switch to the Connect page and change the Format to JSON. Switch to the Web Services page and click the Test button again. o You should now see a Response similar to: NOTE: As of version 2.02.01 of finPOWER Connect, Custom Web Service Scripts can define Parameters which are then used by the Test Web Service form when selecting the Custom, Execute (Parameters) node in the Web Services explorer. This provides a more user-friendly way of testing Custom Web Services that use Parameters. In the above example, adding a Text or Range parameter named 'ClientId' to the Script would allow the Custom Web Service to be tested in this way. Page 35 of 72 Controlling the Response Note that in the above example, the XML response has a root node of <ClientDetails>. This is because we applied an XmlType attribute to the clsClientDetails class. Without this, the root node will have been named <clsClientDetails>. Also, because we have declared the Akas property as a List(Of String), we do not have any control over how this is serialised. Using a custom collection would allow more control. Our custom Web Service switches automatically between XML and JSON serialisation due to the way we are returning the response as an object, i.e.: Return request.CreateResponse(HttpStatusCode.OK, ClientDetails) Alternatively, to return an error response: Return request.CreateErrorResponse(ErrorStatusCode, "Failed to add Client.", ErrorCode, finBL.Error.Message(True, True)) For most services, this is probably desirable but, using the various 'Create' methods of the request object we can force the response to be a particular format as follows: HTML Response CreateHtmlResponse allows us to force the response to be HTML, E.g. Return request.CreateHtmlResponse(HttpStatusCode.OK, "<h1>This is HTML</h1>") This simply sets the HTTP Content-Type header to text/html. JSON Response CreateJsonResponse allows us to force the response to be JSON, E.g. Return request.CreateJsonResponse(HttpStatusCode.OK, "{FirstName: "John", LastName: "Smith"}") This simply sets the HTTP Content-Type header to application/json. Text Response CreateTextResponse allows us to force the response to be plain text, E.g. Return request.CreateTextResponse(HttpStatusCode.OK, Account.AccountId) This simply sets the HTTP Content-Type header to text/plain. NOTE: If a Web Service is designed to return only a very simple value, E.g., the Id of an Account, returning it as plain text means that any calling applications do not need to parse XML or JSON and therefore simplifies the code. XML Response CreateXmlResponse allows us to force the response to be XML, E.g. Return request.CreateXmlResponse(HttpStatusCode.OK, Branch.ToXmlString()) This simply sets the HTTP Content-Type header to text/xml. PDF Response CreatePdfResponse and CreatePdfResponseFromHtml allows us to force the response to be PDF document, E.g. Page 36 of 72 Return request.CreatePdfResponseFromHtml(HttpStatusCode.OK, "<h1>My PDF document</h1>") Or: Dim PdfData() As Byte If finBL.PdfUtilities.CreatePdfByteArrayFromHtml("<h1>My PDF document</h1>", PdfData) Then Return request.CreatePdfResponse(HttpStatusCode.Ok, PdfData) Else ' Error End If This sets the HTTP content to the binary PDF data and sets the Content-Type header to application/pdf. See the PDF Documents section for more information. Page 37 of 72 Accepting Posted Data Sometimes, you may wish to simply POST data directly to a custom Web Service, E.g., an XML document created from an external application. The following Script will accept a POSTed piece of XML in the following format and create a Transaction for an Account. It will not return anything, just an HTTP Status code of 200 if successful: <AccountPayment> <AccountId>L10035</AccountId> <Amount>35.00</Amount> <Reference>REF</Reference> </AccountPayment> Update your custom Web Service's Script code to the following: Public Function Main(request As finwsHttpRequest) As finwsHttpResponse Dim ErrorCode As String Dim ErrorStatusCode As HttpStatusCode Dim Ok As Boolean Dim AccountPayment As finAccountPayment Dim AccountPaymentDetails As clsAccountPaymentDetails Dim Obj As Object ' Assume Success Ok = True ' Initialise ErrorStatusCode = HttpStatusCode.BadRequest ' Parse XML (use business layer helper Function) If finBL.Runtime.WebUtilities.DeserialiseXmlStringToObject(request.RequestText, GetType(clsAccountPaymentDetails), Obj) Then AccountPaymentDetails = DirectCast(Obj, clsAccountPaymentDetails) Else Ok = False End If ' Add Account Payment If Ok Then AccountPayment = finBL.CreateAccountPayment() With AccountPayment ' Load Account Ok = .AccountLoad(AccountPaymentDetails.AccountId) ' Update If Ok Then .TransactionTypeId = "PAY" .PaymentMethodId = "CASHR" .PaymentValue = AccountPaymentDetails.Amount .TransactionReference = AccountPaymentDetails.Reference End If ' Commit If Ok Then Ok = .ExecuteCommit() End If End With End If ' Response If Ok Then Return request.CreateResponse(HttpStatusCode.OK) End If ' Error If Not Ok Then Return request.CreateErrorResponse(ErrorStatusCode, "Failed to make Account payment.", ErrorCode, finBL.Error.Message(True, True)) End If End Function <System.Xml.Serialization.XmlType("AccountPayment")> Page 38 of 72 Public Class clsAccountPaymentDetails Public AccountId As String Public Amount As Decimal Public Reference As String End Class We will now test the updated service using the Test Web Service form. Select the Custom, Execute (POST) node in the Web Services explorer. Enter the Id of your Script, E.g., TESTWS In the Request Text field, past the XML shown at the start of this section. Click the Test button. o You should receive a Response that contains nothing, just an HTTP Status code of 200. The Deserialising the Request and Parsing Posted XML Request sections cover handling Posted data in more detail. Page 39 of 72 Serialising the Response As shown in some of the previous samples in this section, serialisation to either XML or JSON is handled automatically by the Web API and the .NET framework. This section has examples of automatic serialisation of the Response from Script objects. WARNING: Never attempt to return built-in finPOWER Connect business layer objects from a Custom Web Service. These objects are not designed for serialisation. NOTE: Many of the examples in this section remove the following attributes from the response's root XML node for clarity: xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" Page 40 of 72 Nullable Types .NET nullable types are not used within the finPOWER Connect business layer however, they can, and often should, be used when returning the results of a Web Service. For instance if you attempt to return an object that has a Date property, E.g., DateOfBirth, and this does not have a value, it will be returned as 0001-01-01T00:00:00 since the .NET Date type cannot have no value (within .NET, the Date = Nothing assignment and comparison is actually assigning and comparing the date with 0001-01-01T00:00:00). For example, the following Script: Public Function Main(request As finwsHttpRequest) As finwsHttpResponse Dim ClientDetails As ClientDetails ' Create Object ClientDetails = New ClientDetails() With ClientDetails .Name = "Paul" End With ' Return Return request.CreateResponse(HttpStatusCode.OK, ClientDetails) End Function Public Class ClientDetails Public Name As String Public DateOfBirth As Date End Class Returns: <ClientDetails> <Name>Paul</Name> <DateOfBirth>0001-01-01T00:00:00</DateOfBirth> </ClientDetails> If this is modified to use a nullable data type, E.g.: Public Class ClientDetails Public Name As String Public DateOfBirth As Date? End Class The response is much more intuitive: <ClientDetails> <Name>Paul</Name> <DateOfBirth xsi:nil="true" /> </ClientDetails> Nullable types would typically be used for the following: Dates o Only where the date is optional, E.g., a Date of Birth. Numeric types, E.g., Decimal, Double and Integer o Only where returning zero does not make sense. Boolean values o Only where returning True or False does not make sense. Page 41 of 72 NOTE: Strings cannot be nullable since the String data type in the .NET framework is an object and only value types can be nullable. WARNING: Returning nullable types does add an extra level of complexity to the application having to parse the XML and you should consider not including the elements in the response at all as described in the Preventing Properties From Being Serialised section. Page 42 of 72 Dates Dates are serialised using the ISO 8601 standard as detailed in the Dates section. All built-in Web Services return dates containing a time portion in UTC format, regardless of how they are stored within finPOWER Connect. Unless you have a requirement to explicitly ignore this rule, all dates containing a time portion that are returned from a Custom Web Service, E.g., Log dates, should be returned as UTC dates. WARNING: .NET Dates have a Kind property which may affect how the date is serialised. Therefore, when setting dates on objects that are to be serialised, it is important to bear this in mind, E.g., if Kind is DateTimeKind.Local, serialising a date containing a time portion may not produce the result you expect, E.g., the date may be converted to a UTC date when this is not expected. When testing a Custom Web Service, if dates and times are involved, ensure that you test the service on a Web Server running in the time zone that the production server will be using to ensure there are no unforeseen issues. finPOWER Connect contains business layer functionality to convert a dates to nullable, UTC format dates, E.g.: ' Create Object ClientDetails = New ClientDetails() With ClientDetails .Name = Client.Name .DateOfBirth = finBL.Runtime.DateUtilities.CastToNullableUtcDate(Client.DateOfBirth) If LastLog IsNot Nothing Then .LastLogDate = finBL.ToUniversalTime(LastLog.Date) End If End With Produces the following response: <ClientDetails> <Name>Smith, John</Name> <DateOfBirth>1978-02-04T00:00:00Z</DateOfBirth> <LastLogDate>2014-08-07T03:26:11Z</LastLogDate> </ClientDetails> Note that both dates are returned as UTC dates even though the Date of Birth does not contain a time portion. The following helper functions are available in finPOWER Connect business layer: finBL.ToUniversalTime(value) o Converts a date recorded in local time (as Log dates are in finPOWER Connect 2) to UTC time. o The time will be adjusted from local time to UTC time. finBL.Runtime.DateUtilities o CastToNullableUtcDate(value) Casts a date containing no time portion (E.g., a Date of Birth) to a nullable UTC date. If the Date = Nothing then this will be handled correctly. Any time portion is removed. o CastToNullableUtcDateTime(value) Casts a date, optionally containing a time portion to a nullable UTC date. If the Date = Nothing then this will be handled correctly. Page 43 of 72 Does not 'convert' the date to UTC, i.e., no time adjustment is made so this is not suitable for converting local dates to UTC dates. Page 44 of 72 Enums Enums are serialised as String values, E.g., isefinAccountStatus.ClosedPending is serialised to 'ClosedPending' using a method such as finAccount.Status.ToString(). finPOWER Connect generally has an Enum value, E.g., isefinAccountStatus.ClosedPending and also a display value, E.g., 'Closed (Pending)'. This is not the same as the serialised version of the Enum ('ClosedPending') although often the two are the same. WARNING: Do not confuse the finPOWER Connect display value for the Enum which is achieved via methods such as finBL.Enums.isefinAccountStatus_ToString() methods. These methods are used for displaying Enum values in a user-readable form, E.g., including spaces or varying the display value based upon the database country. When returning an object that contains an Enum value, it may be desirable to return both the Enum and the display value. Typically, the property holding the display value is named the same as the Enum value property but with a 'Text' suffix, E.g.: Public Function Main(request As finwsHttpRequest) As finwsHttpResponse Dim Account As finAccount Dim AccountDetails As AccountDetails ' Load (ignore errors for simplicity) Account = finBL.CreateAccount() Account.Load("L10000") ' Create Object AccountDetails = New AccountDetails() With AccountDetails .Name = Account.Name .Status = Account.Status .StatusText = Account.StatusText End With ' Return Return request.CreateResponse(HttpStatusCode.OK, AccountDetails) End Function Public Class AccountDetails Public Name As String Public Status As isefinAccountStatus Public StatusText As String End Class Produces the following response: <AccountDetails> <Name>Smith, John</Name> <Status>ClosedPending</Status> <StatusText>Closed (Pending)</StatusText> </AccountDetails> Page 45 of 72 Using Attributes to Tweak Serialisation You may wish to serialise classes using an XML element named differently from the class name. This is achieved by applying the XmlType or XmlRoot attributes to the class, E.g.: <System.Xml.Serialization.XmlType("Transaction")> Public Class finwsAccountTransaction ' Properties Public [Date] As Date Public ElementId As String Public Reference As String Public Value As Decimal End Class < System.Xml.Serialization.XmlRoot("Transactions")> Public Class finwsAccountTransactions : Inherits List(Of finwsAccountTransaction) End Class < System.Xml.Serialization.XmlType("AvailableCredit")> Public Class finwsAccountAvailableCreditDetails Public Public Public Public Public Public AvailableCredit1 As Decimal AvailableCredit2 As Decimal AvailableCredit3 As Decimal CreditLimit1 As Decimal CreditLimit2 As Decimal CreditLimit3 As Decimal End Class NOTE: Both of these attributes work in a similar way in that they determine the name of the XML element containing their properties. Page 46 of 72 Preventing Properties From Being Serialised Sometimes, you may wish to define properties on your object but not have them serialised under certain circumstances. For example, you may have a ClientDetails class that contains properties that should only be serialised for 'Individual' type Clients, E.g., DateOfBirth. NOTE: These ShouldSerialize methods are applied when serialising as XML or JSON and are built-in to the .NET framework. The method MUST be named after the property name, E.g., to control whether a DateOfBirth property is serialised, you must have a method named ShouldSerializeDateOfBirth() that returns a Boolean value. Also note the U.S. spelling of the word 'serialize'. The following example shows how to achieve this using special ShouldSerialize methods which are used by the .NET serialisation process to decide whether or not to serialise a property: Public Function Main(request As finwsHttpRequest) As finwsHttpResponse Dim Client As finClient Dim ClientDetails As ClientDetails ' Load (ignore errors for simplicity) Client = finBL.CreateClient() Client.Load("C10000") ' Create Object ClientDetails = New ClientDetails(Client) With ClientDetails .Name = Client.Name .DateOfBirth = finBL.Runtime.DateUtilities.CastToNullableUtcDate(Client.DateOfBirth) .OrganisationNumber = Client.OrganisationNumber End With ' Return Return request.CreateResponse(HttpStatusCode.OK, ClientDetails) End Function Public Class ClientDetails ' Properties Public Name As String Public DateOfBirth As Date? Public OrganisationNumber As String ' Other Private mClient As finClient Public Sub New(client As finClient) mClient = client End Sub Public Sub New() End Sub ' Prevent Serialisation for Not Applicable Properties Public Function ShouldSerializeDateOfBirth() As Boolean Return mClient Is Nothing OrElse mClient.IsIndividual OrElse mClient.IsSoleTrader End Function Public Function ShouldSerializeOrganisationNumber() As Boolean Return mClient Is Nothing OrElse mClient.IsOrganisation End Function End Class Page 47 of 72 Produces the following response for an 'Individual' Client: <ClientDetails> <Name>Smith, John</Name> <DateOfBirth>1978-02-04T00:00:00Z</DateOfBirth> </ClientDetails> And the following response for an 'Organisation' Client: <ClientDetails> <Name>Company Limited</Name> <OrganisationNumber>1234567890</OrganisationNumber> </ClientDetails> WARNING: Serialisable classes must always have an empty, public constructor which is why the above example has two constructors. This is also why the test mClient Is Nothing is performed since theoretically, a ClientDetails object could be created without passing in a finClient object. Page 48 of 72 Serialising Collections All of the above examples have dealt with serialising a single-level object. The following example shows how to return a collection object: Public Function Main(request As finwsHttpRequest) As finwsHttpResponse Dim Dim Dim Dim Account As finAccount AccountTransaction As finAccountTransaction AccountDetails As AccountDetails Transaction As Transaction ' Load (ignore errors for simplicity) Account = finBL.CreateAccount() Account.Load("L10000") ' Create Object AccountDetails = New AccountDetails() With AccountDetails .AccountId = Account.AccountId .Name = Account.Name .Transactions = New List(Of Transaction) End With ' Add Transactions For Each AccountTransaction In Account.Transactions ' Create Transaction = New Transaction() ' Update With AccountTransaction Transaction.Date = finBL.Runtime.DateUtilities.CastToUtcDate(.Date) Transaction.ElementId = .ElementId Transaction.Value = .Value End With ' Add to Collection AccountDetails.Transactions.Add(Transaction) Next ' Return Return request.CreateResponse(HttpStatusCode.OK, AccountDetails) End Function Public Class AccountDetails Public AccountId As String Public Name As String Public Transactions As List(Of Transaction) End Class Public Class Transaction Public [Date] As Date Public ElementId As String Public Value As Decimal End Class Produces the following response: <AccountDetails> <AccountId>L10000</AccountId> <Name>Smith, John</Name> <Transactions> <Transaction> <Date>2014-06-08T00:00:00Z</Date> <ElementId>ADV</ElementId> <Value>10000</Value> </Transaction> <Transaction> <Date>2014-06-09T00:00:00Z</Date> <ElementId>ACCF</ElementId> <Value>123</Value> </Transaction> Page 49 of 72 <Transaction> <Date>2014-07-09T00:00:00Z</Date> <ElementId>ACCF</ElementId> <Value>2.22</Value> </Transaction> <Transaction> <Date>2014-07-09T00:00:00Z</Date> <ElementId>PAY</ElementId> <Value>-4.22</Value> </Transaction> </Transactions> </AccountDetails> Page 50 of 72 Deserialising the Request As shown in the Accepting Posted Data section, it is often desirable to deserialise XML posted to the Custom Web Service into an object that the Script can use. WARNING: Deserialisation is useful for small, flat objects or simple collections. For complex XML it may be more suitable to parse the posted XML directly. The finPOWER Connect business layer provides functionality to deserialise XML into an object as shown in the following example: Public Function Main(request As finwsHttpRequest) As finwsHttpResponse Dim ErrorCode As String Dim ErrorStatusCode As HttpStatusCode Dim Ok As Boolean Dim Client As finClient Dim ClientDetails As ClientDetails Dim Obj As Object ' Assume Success Ok = True ' Initialise ErrorStatusCode = HttpStatusCode.BadRequest ' Parse XML (use business layer helper Function) If finBL.Runtime.WebUtilities.DeserialiseXmlStringToObject(request.RequestText, GetType(ClientDetails), Obj) Then ClientDetails = DirectCast(Obj, ClientDetails) Else Ok = False End If ' Add Client If Ok Then Client = finBL.CreateClient() With Client ' Update .FirstName = ClientDetails.FirstName .LastName = ClientDetails.LastName .DateOfBirth = ClientDetails.DateOfBirth ' Save Ok = .Save() End With End If ' Return Response If Ok Then Return request.CreateResponse(HttpStatusCode.OK, Client.ClientId) Else Return request.CreateErrorResponse(ErrorStatusCode, "Failed to add Client.", ErrorCode, finBL.Error.Message(True, True)) End If End Function Public Class ClientDetails Public FirstName As String Public LastName As String Public DateOfBirth As Date End Class The following XML can be posted to the above Script: <ClientDetails> <FirstName>Paul</FirstName> <LastName>Jones</LastName> <DateOfBirth>1986-03-17</DateOfBirth> </ClientDetails> Page 51 of 72 Points of note in the above example are: Use of the DeserialiseXmlStringToObject() helper method. o Internally, this uses the .NET framework's deserialisation functionality. ClientDetails.DateOfBirth is defined as a date. o This means that if the XML omits this tag, the property will remain as the default value for a Date which equals Nothing, therefore there is no special handling required. o o This is a Date with no time portion hence there is no need to convert from a UTC date. Supplying an empty DateOfBirth element in the XML, E.g.: <DateOfBirth/> Will cause a deserialisation error. Either omit the element completely or use the following format: <ClientDetails xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"> <FirstName>Paul</FirstName> <LastName>Jones</LastName> <DateOfBirth xsi:nil="true" /> </ClientDetails> And change the DateOfBirth property to a nullable type: Public Class ClientDetails Public FirstName As String Public LastName As String Public DateOfBirth As Date? End Class And handle accordingly when updating the finClient object, E.g.: If ClientDetails.DateOfBirth IsNot Nothing Then .DateOfBirth = CDate(ClientDetails.DateOfBirth) End If Page 52 of 72 Testing Posted XML Testing a Custom Web Service that accepts posted XML can be achieved from the Test Web Services form. Simply paste a sample of the XML into the 'Request Text' field, E.g.: Page 53 of 72 Enums Enums are included in XML using their String representation, E.g., isefinAccountStatus.ClosedPending would be serialised using a method such as finAccount.Status.ToString(). This would produce a value of 'ClosedPending'. WARNING: Do not confuse this with the finPOWER Connect display value for the Enum which is achieved via methods such as finBL.Enums.isefinAccountStatus_ToString() methods. These methods are used for displaying Enum values in a user-readable form, E.g., including spaces or varying the display value based upon the database country. Deserialisation of Enums is handed internally by the .NET framework however, it is not recommended that you use nullable types for Enums since this complicates the deserialisation process. Therefore, do not include an Enum tag in the XML if it is not required. Page 54 of 72 Using Attributes to Tweak Deserialisation By default, XML will be deserialised into properties that match the XML element names. This can be tweaked using attributes, E.g.: <System.Xml.Serialization.XmlType("TransactionBatch")> Public Class clsTransactionBatch Public BatchId As String Public TransactionType As String Public Transactions As List(Of clsTransaction) End Class <System.Xml.Serialization.XmlType("Transaction")> Public Class clsTransaction Public Public Public Public Public AccountId As String [Date] As Date Reference As String ElementId As String Value As Decimal End Class Page 55 of 72 Deserialising Collections Collections can also be deserialied as shown in the following example which creates a Batch of Transactions and saves and then commits the Batch: Public Function Main(request As finwsHttpRequest) As finwsHttpResponse Dim Dim Dim Dim Dim Dim Dim Dim Batch As finBatch BatchTransaction As finBatchTransaction ErrorCode As String ErrorStatusCode As HttpStatusCode Obj As Object Ok As Boolean Transaction As clsTransaction TransactionBatch As clsTransactionBatch ' Assume Success Ok = True ' Initialise ErrorStatusCode = HttpStatusCode.BadRequest ' Deserialise the XML to an Object If finBL.Runtime.WebUtilities.DeserialiseXmlStringToObject(request.RequestText, GetType(clsTransactionBatch), Obj) Then TransactionBatch = DirectCast(Obj, clsTransactionBatch) Else Ok = False End If If Ok Then ' Initialise Batch = finBL.CreateBatch() ' Setup Batch With Batch .BatchId = TransactionBatch.BatchId .TransactionTypeId = TransactionBatch.TransactionType .SourceSet(isefinTransactionSource.ExternalA) End With ' Add Transactions For Each Transaction In TransactionBatch.Transactions ' Create BatchTransaction = Batch.Transactions.CreateBatchTransaction() ' Update (ignore errors for simplicity of sample) With BatchTransaction .AccountIdSet(Transaction.AccountId) .ElementIdSet(Transaction.ElementId) .Date = Transaction.Date .Value = Transaction.Value .Reference = Transaction.Reference End With ' Add Batch.Transactions.Add(BatchTransaction) Next ' Save Batch Ok = Batch.Save() ' Commit Batch If Ok Then Ok = Batch.ExecuteCommit(False) End If End If ' Return Response If Ok Then Return request.CreateResponse(HttpStatusCode.OK) Else Return request.CreateErrorResponse(ErrorStatusCode, String.Format("Failed to execute custom Web Service Script '{0}'.", ScriptInfo.ScriptId), ErrorCode, finBL.Error.Message(True, True)) End If End Function <System.Xml.Serialization.XmlType("TransactionBatch")> Page 56 of 72 Public Class clsTransactionBatch Public BatchId As String Public TransactionType As String Public Transactions As List(Of clsTransaction) End Class <System.Xml.Serialization.XmlType("Transaction")> Public Class clsTransaction Public Public Public Public Public AccountId As String [Date] As Date Reference As String ElementId As String Value As Decimal End Class The following XML can be posted to the above Script: <TransactionBatch> <BatchId>WSBatch001</BatchId> <TransactionType>PAY</TransactionType> <Transactions> <Transaction> <AccountId>L10000</AccountId> <Date>2014-08-07</Date> <Reference>Reference 1</Reference> <ElementId>PAY</ElementId> <Value>100.00</Value> </Transaction> <Transaction> <AccountId>L10035</AccountId> <Date>2014-08-08</Date> <Reference>Reference 2</Reference> <ElementId>PAY</ElementId> <Value>42.50</Value> </Transaction> </Transactions> </TransactionBatch> Points of note in the above example are: Use of the DeserialiseXmlStringToObject() helper method. o Internally, this uses the .NET framework's deserialisation functionality. o This deserialises the <Transactions> block in to the Transactions property which is defined as a List(Of clsTransaction). Attributes (E.g., <System.Xml.Serialization.XmlType("Transaction")>) are used to map the class names, E.g., clsTrasaction to an XML element name, E.g., Transaction. Page 57 of 72 Troubleshooting Deserialisation Issues Problems can arise when attempting to deserialise XML into an object and these are often hard to track. The DeserialiseXmlStringToObject() method will return False if deserialisation failed. This may be due to the following: The XML being deserialised was invalid. Dates or numbers within the XML are invalid. o E.g., Date values are not in the ISO 8601 format. ISO 8601: 2014-08-16 Non-standard format: 8/16/2014 Dates or numbers elements in the XML are empty. o Even if the property on the object that the element is being serialised into is a nullable type, including an empty element will still produce an error unless the xsi:nil="true" attribute is applied as described in the 'Date of Birth' example at the end of the Deserialising the Request section. Additionally, if the property name in the object does not exactly match the XML element name (this is case-sensitive), the property will not be populated during the deserialisation process. Page 58 of 72 Parsing Posted XML Request Parsing the posted XML request manually may be a better option than deserialising it if: The XML is complex, E.g., many levels of nesting. You want more control than the deserialisation process can provide, E.g.: o The XML has different versions, E.g., a 'version' attribute on the root node affects how the XML should be parsed. o The XML contains dates not formatted according to the ISO 8601 standard. This may be the case if the supplied XML is in a legacy format of comes from a source that you do not have control over. The finPOWER Connect business layer has helper methods to assist in the parsing of XML. The following example shows how to manually parse the XML used in Deserialising Collections sample: Public Function Main(request As finwsHttpRequest) As finwsHttpResponse Dim Dim Dim Dim Dim Dim Dim Dim Batch As finBatch BatchTransaction As finBatchTransaction ErrorCode As String ErrorStatusCode As HttpStatusCode Ok As Boolean XmlDocument As XmlDocument Node As XmlNode Nodes As XmlNodeList ' Assume Success Ok = True ' Initialise ErrorStatusCode = HttpStatusCode.BadRequest ' Parse XML With finBL.Runtime.XmlUtilities ' Load XML Document If .LoadXmlDocumentFromString(request.RequestText, XmlDocument) Then ' Initialise Batch = finBL.CreateBatch() ' Get Root Node Node = XmlDocument.SelectSingleNode("TransactionBatch") If Node Is Nothing Then Ok = False finBL.Error.ErrorBegin("TransactionBatch node not found.") End If ' Setup Batch If Ok Then Batch.BatchId = .GetSubNodeString(Node, "BatchId") Batch.TransactionTypeId = .GetSubNodeString(Node, "TransactionType") Batch.SourceSet(isefinTransactionSource.ExternalA) End If ' Transactions If Ok Then Nodes = Node.SelectNodes("Transactions/Transaction") For Each Node In Nodes ' Create BatchTransaction = Batch.Transactions.CreateBatchTransaction() ' Update (ignore errors for simplicity of sample) BatchTransaction.AccountIdSet(.GetSubNodeString(Node, "AccountId")) BatchTransaction.ElementIdSet(.GetSubNodeString(Node, "ElementId")) BatchTransaction.Date = .GetSubNodeDate(Node, "Date") BatchTransaction.Value = .GetSubNodeDecimal(Node, "Value") BatchTransaction.Reference = .GetSubNodeString(Node, "Reference") ' Add Batch.Transactions.Add(BatchTransaction) Next Page 59 of 72 End If ' Save Batch If Ok Then Ok = Batch.Save() End If ' Commit Batch If Ok Then Ok = Batch.ExecuteCommit(False) End If Else Ok = False End If End With ' Return Response If Ok Then Return request.CreateResponse(HttpStatusCode.OK) Else Return request.CreateErrorResponse(ErrorStatusCode, String.Format("Failed to execute custom Web Service Script '{0}'.", ScriptInfo.ScriptId), ErrorCode, finBL.Error.Message(True, True)) End If End Function Points of note in the above example are: Use of the finBL.Runtime.XmlUtilities helper methods to load and parse XML. Page 60 of 72 Serialising and Deserialising XML Some programming languages support serialisation and deserialisation of XML from and to an object. In many cases, this can make parsing a response (or forming a request) easier than dealing directly with XML. NOTE: This section is intended for external applications consuming the Web Services. For Custom Web Service Scripts, see the Serialising the Response and Deserialising the Request sections. In the Custom Web Services section, the finPOWER Connect business layer was used to serialise a response to XML (or JSON). This section gives code samples of both serialisation and deserialisation. Serialisation/ Deserialisation can be used with complex objects and collections (E.g., objects that contain other objects). A Google search will provide many examples of how to achieve this. Serialisation Serialisation is the process of taking an object and automatically creating an XML String that represents that object. The following examples will all serialise the simple ClientDetails object detailed below into XML: ClientDetails ClientId (String) FirstName (String) LastName (String) DateOfBirth (Date) Each of the code samples contains a SerialiseObjectToXmlString function which returns a Boolean value based upon whether it was successful or not. If unsuccessful, an error message is returned instead of the XML. Page 61 of 72 Visual Basic Public Sub Main() Dim ClientDetails As clsClientDetails Dim Xml As String ' Create Object ClientDetails = New clsClientDetails() With ClientDetails .ClientId = "C10000" .FirstName = "John" .LastName = "Smith" .DateOfBirth = New Date(1970, 9, 5) End With ' Serialise If SerialiseObjectToXmlString(ClientDetails, Xml) Then MsgBox(Xml) Else MsgBox(Xml, MsgBoxStyle.Exclamation) End If End Sub Public Function SerialiseObjectToXmlString(obj As Object, ByRef xml As String) As Boolean Dim ms As System.IO.MemoryStream Dim Ok As Boolean Dim XmlSerializer As System.Xml.Serialization.XmlSerializer ' Assume Success Ok = True ' Initialise ByRef Parameters xml = "" ' Serialise Try XmlSerializer = New System.Xml.Serialization.XmlSerializer(obj.GetType()) ms = New System.IO.MemoryStream() XmlSerializer.Serialize(ms, obj) xml = System.Text.Encoding.UTF8.GetString(ms.GetBuffer(), 0, CInt(ms.Length)) ms.Close() Catch ex As Exception ' Failed (return error message instead of XML) Ok = False xml = ex.Message Finally If ms IsNot Nothing Then ms.Dispose() End Try Return Ok End Function <System.Xml.Serialization.XmlType("ClientDetails")> Public Class clsClientDetails Public ClientId As String Public FirstName As String Public LastName As String Public DateOfBirth As Date End Class Page 62 of 72 C# public void Main() { clsClientDetails ClientDetails; string Xml = ""; // Create Object ClientDetails = new clsClientDetails { ClientId = "C10000", FirstName = "John", LastName = "Smith", DateOfBirth = new DateTime(1970, 9, 5) }; // Serialise if(SerialiseObjectToXmlString(ClientDetails, ref Xml)) { System.Windows.Forms.MessageBox.Show(Xml); } else { System.Windows.Forms.MessageBox.Show(Xml, "Error", MessageBoxButtons.OK, MessageBoxIcon.Warning); } } public bool SerialiseObjectToXmlString(Object obj, ref string xml) { System.IO.MemoryStream ms = null; bool Ok; System.Xml.Serialization.XmlSerializer XmlSerializer; // Assume Success Ok = true; // Initialise ByRef Parameters xml = ""; // Serialise try { XmlSerializer = new System.Xml.Serialization.XmlSerializer(obj.GetType()); ms = new System.IO.MemoryStream(); XmlSerializer.Serialize(ms, obj); xml = System.Text.Encoding.UTF8.GetString(ms.GetBuffer(), 0, (int)ms.Length); ms.Close(); } catch(Exception ex) { // Failed (return error message instead of XML) Ok = false; xml = ex.Message; } finally { if(ms != null) ms.Dispose(); } return Ok; } [System.Xml.Serialization.XmlType("ClientDetails")] public class clsClientDetails { public string ClientId; public string FirstName; public string LastName; public DateTime DateOfBirth; } Page 63 of 72 Deserialisation Deserialisation is the process of taking an XML String and automatically creating and populating an object from the content of the XML. The following examples will deserialise the following XML into the simple ClientDetails object used in the previous section. <ClientDetails> <ClientId>C10000</ClientId> <FirstName>John</FirstName> <LastName>Smith</LastName> <DateOfBirth>1970-09-05T00:00:00</DateOfBirth> </ClientDetails> Each of the code samples contains a DeserialiseObjectFromXmlString function which returns a Boolean value based upon whether it was successful or not. If unsuccessful, an error message is returned instead of the deserialised object. Page 64 of 72 Visual Basic Public Sub Main() Dim ClientDetails As clsClientDetails Dim tempObject As Object Dim Xml As String ' Define XML Xml &= "<ClientDetails>" Xml &= "<ClientId>C10000</ClientId>" Xml &= "<FirstName>John</FirstName>" Xml &= "<LastName>Smith</LastName>" Xml &= "<DateOfBirth>1970-09-05T00:00:00</DateOfBirth>" Xml &= "</ClientDetails>" ' Deserialise into Object If DeserialiseXmlStringToObject(Xml, GetType(clsClientDetails), tempObject) Then ClientDetails = DirectCast(tempObject, clsClientDetails) MsgBox("Deserialised ClientId=" & ClientDetails.ClientId) Else MsgBox(CStr(tempObject), MsgBoxStyle.Exclamation) End If End Sub Public Function DeserialiseXmlStringToObject(xml As String, objType As Type, ByRef obj As Object) As Boolean Dim ms As System.IO.MemoryStream Dim Ok As Boolean Dim XmlSerializer As System.Xml.Serialization.XmlSerializer ' Assume Success Ok = True ' Initialise ByRef Parameters obj = Nothing ' Deserialise Try ' Create Serialiser XmlSerializer = New System.Xml.Serialization.XmlSerializer(objType) ' Deserialise ms = New System.IO.MemoryStream(System.Text.Encoding.UTF8.GetBytes(xml)) obj = XmlSerializer.Deserialize(ms) ms.Close() Catch ex As Exception Ok = False obj = ex.Message Finally If ms IsNot Nothing Then ms.Dispose() End Try Return Ok End Function <System.Xml.Serialization.XmlType("ClientDetails")> Public Class clsClientDetails Public ClientId As String Public FirstName As String Public LastName As String Public DateOfBirth As Date End Class Page 65 of 72 C# public void Main(object sender, EventArgs e) { clsClientDetails ClientDetails; Object tempObject = null; string Xml; // Define XML Xml = ""; Xml += "<ClientDetails>"; Xml += "<ClientId>C10000</ClientId>"; Xml += "<FirstName>John</FirstName>"; Xml += "<LastName>Smith</LastName>"; Xml += "<DateOfBirth>1970-09-05T00:00:00</DateOfBirth>"; Xml += "</ClientDetails>"; // Deserialise into Object if(DeserialiseXmlStringToObject(Xml, typeof(clsClientDetails), ref tempObject)) { ClientDetails = (clsClientDetails)tempObject; System.Windows.Forms.MessageBox.Show("Deserialised ClientId=" + ClientDetails.ClientId); } else { System.Windows.Forms.MessageBox.Show((string)tempObject, "Error", MessageBoxButtons.OK, MessageBoxIcon.Warning); } } bool DeserialiseXmlStringToObject(string xml, Type objType, ref Object obj) { System.IO.MemoryStream ms = null; bool Ok; System.Xml.Serialization.XmlSerializer XmlSerializer; // Assume Success Ok = true; // Initialise ByRef Parameters obj = null; // Deserialise try { // Create Serialiser XmlSerializer = new System.Xml.Serialization.XmlSerializer(objType); // Deserialise ms = new System.IO.MemoryStream(System.Text.Encoding.UTF8.GetBytes(xml)); obj = XmlSerializer.Deserialize(ms); ms.Close(); } catch (Exception ex) { Ok = false; obj = ex.Message; } finally { if (ms != null) ms.Dispose(); } return Ok; } [System.Xml.Serialization.XmlType("ClientDetails")] public class clsClientDetails { public string ClientId; public string FirstName; public string LastName; public DateTime DateOfBirth; } Page 66 of 72 PDF Documents Web Service functionality exists to convert HTML to a PDF document. NOTE: finPOWER Connect uses an external component to convert HTML to a PDF document therefore the formatting is outside of the control of Intersoft Systems. Returning a PDF Document from a Custom Web Service Custom Web Services can return a Response representing a PDF document using either the request.CreatePdfResponseFromHtml or request.CreatePdfResponse methods. The following example returns a simple PDF document: Option Explicit On Option Strict On Public Function Main(request As finwsHttpRequest) As finwsHttpResponse Dim ErrorCode As String Dim ErrorStatusCode As HttpStatusCode Dim Ok As Boolean ' Assume Success Ok = True ' Initialise ErrorStatusCode = HttpStatusCode.BadRequest ' Return Response If Ok Then Return request.CreatePdfResponseFromHtml(HttpStatusCode.OK, "<h1>My PDF document</h1>.") Else Return request.CreateErrorResponse(ErrorStatusCode, String.Format("Failed to execute custom Web Service Script '{0}'.", ScriptInfo.ScriptId), ErrorCode, finBL.Error.Message()) End If End Function This returns the PDF file content as binary data. The following is a sample response with the majority of the HTTP body stripped out since it is meaningless: HTTP/1.1 200 OK Content-Length: 2401 Content-Type: application/pdf Date: Wed, 04 Jun 2014 00:29:48 GMT Expires: -1 Server: Microsoft-IIS/8.0 X-AspNet-Version: 4.0.30319 X-Powered-By: ASP.NET %PDF-1.5 %���� %Created by EVO PDF Tools v3.5 Note that the Content Type header specifies application/pdf. A consumer of this Web Service could then retrieve the response data as am array of bytes and either write it to a file or stream it to the user's Web browser. Page 67 of 72 Returning a Response Containing a PDF Document Data from a Custom Web Service In the previous section, a custom Web Service was used to return a PDF document. The response from the custom Web Service was a complete PDF document and contained no other information. In certain situations, it may be desirable to return the PDF document as part of a more complex response, E.g., an XML response containing a <PdfDocument> node. The following example returns a resonse containing Base 64 encoded PDF data. This can then be decoded by the Web Service consumer and written to a PDF file: Option Explicit On Option Strict On Public Function Main(request As finwsHttpRequest) As finwsHttpResponse Dim Dim Dim Dim Dim ErrorCode As String ErrorStatusCode As HttpStatusCode Ok As Boolean PdfDetails As PdfDetails PdfDocumentBase64 As String ' Assume Success Ok = True ' Initialise ErrorStatusCode = HttpStatusCode.BadRequest ' Create PDF Document from HTML Ok = finBL.PdfUtilities.CreatePdfBase64StringFromHtml("<h1>My PDF Document</h1>", PdfDocumentBase64) ' Create PDF Details If Ok Then PdfDetails = New PdfDetails() With PdfDetails .Title = "PDF Title" .PdfDocument = PdfDocumentBase64 End With End If ' Return Response If Ok Then ' Return PDF Details Return request.CreateResponse(HttpStatusCode.OK, PdfDetails) Else ' Error Return request.CreateErrorResponse(ErrorStatusCode, String.Format("Failed to execute custom Web Service Script '{0}'.", ScriptInfo.ScriptId), ErrorCode, finBL.Error.Message()) End If End Function Public Class PdfDetails Public Title As String Public PdfDocument As String End Class Since this Script returns an object (PdfDetails), it will automatically be serialised as either XML or JSON as per the following (stripped down) responses: <PdfDetails> <Title>PDF Title</Title> <PdfDocument>JVBERi0xLjUNCiWxsrO0DQolQ3JlYXRlZCBieSBFVk8gUERGIFRvb2xzIHYzLjUNCjEgMCBvYmoNCjw8DQovUGF nZXMgMiAwIFINCi9QYWdlTGF5b3V0IC9PbmVDb2x1bW4NCi9QYWdlTW9kZSAvVXNlTm9uZQ0KL1ZpZXdlclByZWZlcmVuY2VzIDM gMCBSDQovVHlwZSAvQ2F0YWxvZw0KPj4NCg0KZW5kb2JqDQo2IDAgb2JqDQo8PA0KL0ZpbHRlciAvRmxhdGVEZWNvZGUNCi9MZW5 ndGggMTMzDQo+Pg0Kc3RyZWFt</PdfDocument> </PdfDetails> Page 68 of 72 {"Title":"PDF Title", "PdfDocument":"JVBERi0xLjUNCiWxsrO0DQolQ3JlYXRlZCBieSBFVk8gUERGIFRvb2xzIHYzLjUNCjEgMCBvYmoNCjw8DQovU GFnZXMgMiAwIFINCi9QYWdlTGF5b3V0IC9PbmVDb2x1bW4NCi9QYWdlTW9kZSAvVXNlTm9uZQ0KL1ZpZXdlclByZWZlcmVuY2VzI DMgMCBSDQovVHlwZSAvQ2F0YWxvZw0KPj4NCg0KZW5kb2JqDQo2IDAgb2JqDQo8PA0KL0ZpbHRlciAvRmxhdGVEZWNvZGUNCi9MZ W5ndGggMTMyDQo+Pg0Kc3RyZWFt"} Formatting PDF Documents finPOWER Connect contains functionality to convert HTML into a PDF via the finBL.PdfUtilities class. When creating PDF documents from HTML, note the following: The PDF file is generated on the Web Server on which the Web Services are running and can therefore only access any fonts available on this Server. o You may wish to use safe fonts such as: Arial Times New Roman Verdana Any images you wish to include in the PDF, E.g., company logos should be referenced via a URL that can be resolved by the Web Server. You do not have the same amount of control over the document that is produced as you would using a Word processing package such as Microsoft Word. By default, the HTML is formatted as if displayed in a browser window that is 1024 pixels wide. o This can be changed via the ViewportWidth meta tag detailed below. The following meta tags can be included at the top of the HTML to tweak that PDF file that is produced: <meta <meta <meta <meta <meta <meta <meta name='pdf-PageSize' content='A4'/> name='pdf-PageOrientation' content='Portrait'/> name='pdf-LeftMargin' content='1.5cm'/> name='pdf-RightMargin' content='1cm'/> name='pdf-TopMargin' content='1.5cm'/> name='pdf-BottomMargin' content='2.54cm'/> name='pdf-ViewportWidth' content='1200'/> PageSize o One of the following: A4, A5, Legal, Letter PageOrientation o Either Portrait or Landscape LeftMargin, RightMargin, TopMargin, BottomMargin o Can be specified in the following units: cm, inches (if omitted, points are assumed) ViewportWidth o This defaults to 1024 but specifying a different value will vary how the PDF is generated, E.g., how much information fits on a line. By changing this value, the HTML text may scale to a different size so for consistency, it may be best to not set the ViewportWidth but to change the font size in the HTML using CSS. Page 69 of 72 Visual Basic Code Examples Visual Basic code examples are included in the /Samples folder of the Web Services. All of these samples are ASP.NET Web Forms and therefore contain a .aspx and a .aspx.vb file. The following table lists these samples: Sample Description Other Details ConnectUser1VB.aspx Connect as a finPOWER Connect User and retrieve a list of Client Accounts. CustomLoanQuote1VB.aspx Enter simple Loan and Client details and use a custom Web Service to create a Quote Account and a Client. Script CustomLoanQuote1VB.xml must be imported into finPOWER Connect. To use get these samples up and running: Create a new empty Web Application in Visual Studio 2012 (or Visual Studio Express 2012 for Web). o Keep the default project name of WebApplication1. The sample .aspx files reference a namespace of WebApplication1 so doing this saves having to modify the samples' .aspx portion. Copy the entire contents of the Web Services /Samples folder into the new Web Application. o Include all files (except for any .xml files) in the Web Application project. Ensure you have a Web Subscriber record set up in finPOWER Connect. o In finPOWER Connect, open the database that the Web Services are connected to. o From the Tools menu, select Web and then Web Subscribers. o Add a new Web Subscriber record with the following details: Code: TEST Description: Test Web Subscriber View the .vb portion of all sample pages and update the following constants to allow connection to the Web Services: o WEB_SERVICES_URL o WEB_SUBSCRIBER_ID o WEB_SUBSCRIBER_SECRET_KEY Run the Web Application and ensure that the ConnectUser1VB.aspx page runs correctly. o This is the simplest example, therefore ensure this runs correctly before attempting to run any other examples. Some samples may require a custom Web Service Script. Page 70 of 72 o The following samples require that their corresponding .xml file is imported into the finPOWER Connect Scripts library: CustomLoanQuote1VB.aspx o To import the Script, do the following: In finPOWER Connect, open the database that the Web Services are connected to. From the Admin menu, select Scripts. Use the Import action to import the .xml file. Save the Script. Page 71 of 72 Troubleshooting Timeout when Authenticating Client A timeout error when attempting to authenticate a Client via the Authentication/AuthenticateClient service may be due to the following: Misconfigured Address Database If the Address database being used by the finPOWER Connect business layer is not available, attempting to connect to it may cause a time out. This may be an issue when attempting to authenticate as a Client since the response from this service includes formatted Branch address details which involves accessing the Addressing interface which will always attempt to initialise a connection to the Address database when first accessed. Page 72 of 72