In certain situations, it is necessary for the gateway service to communicate with e-Forms. The following article contains a number of tips and best practices on how to establish that communication.
MS Visual Studio is one of the best practices. The code and the helpers can also be created in Neurodot e-Forms Designer, however it might be more comfortable for the user to create it in MS Visual Studio instead. When the external library for e-Forms is created, it has to be tested before it is used in e-Forms (e.g. the user can create a console application and then debug the external library on their computer) since the debuging function is not available in the e-Forms Designer.
BootstrapToken is saved in the users's credentials and the hosting web must have it saved for e-Forms.
Read BootstrapToken and save it into a variable on a html page.
<%
var bootstrapContext = System.Security.Claims.ClaimsPrincipal.Current.Identities.First().BootstrapContext as BootstrapContext;
var bToken = "";
if (bootstrapContext != null)
{
bToken = Convert.ToBase64String(Encoding.UTF8.GetBytes(bootstrapContext.Token));
}
%>
<script>
var bootstrapToken = "<%= bToken %>";
</script>
These other values have to be specified in hosting web, for example in web.config.
The sample for loading values from web.config:
<script>
var submitUrl = "<%= ConfigurationManager.AppSettings["GGSubmissionEndpoint"]%>";
var appliesTo = "<%= ConfigurationManager.AppSettings["GGSubmissionAppliesTo"]%>";
var actasUrl = "<%= ConfigurationManager.AppSettings["GGFederationActAsEndpoint"]%>";
</script>
The following table contains sample values.
Name | Value |
---|---|
GGSubmissionEndpoint | https:///ws/submission/rest.svc/submit |
GGSubmissionAppliesTo | https:///ws/submission/public.svc/token |
GGFederationActAsEndpoint | https:///FPSTS/oauth2/actas |
After acquiring the required values in the hosting page, they need to be saved to the session for eForms to be available in eFrom ND code. The values have to be saved before a new eForm is opened.
<script>
function SaveDataForForm() {
SetSessionData('SECURITY_TOKEN', bootstrapToken, function () { });
SetSessionData('SUBMIT_URL', submitUrl, function () { });
SetSessionData('APPLIES_TO', appliesTo, function () { });
SetSessionData('ACTAS_URL', actasUrl, function () { });
}
</script>
The above sample saves the required values (SubmitUrl, BootstrapToken ... ) every time a new eForm is opened. However, not every eForm needs these values, therefore a better solution would be to save the metadata of the eForm which the user wishes to open.
eForms support loading data from a session. The following command can be used: "ThisForm.GetSessionData("< name of parameter >")". The values are loaded in the event handler for event Forms.Loaded.
static string submitUrl = "";
static string appliesTo = "";
static string actasUrl = "";
static string btoken = "";
public static void Loaded(object sender, System.Windows.RoutedEventArgs e)
{
try
{
submitUrl = ThisForm.GetSessionData("SUBMIT_URL");
if(string.IsNullOrWhiteSpace(submitUrl))
{
// TODO : data is empty, show some error message
return;
}
appliesTo = ThisForm.GetSessionData("APPLIES_TO");
if(string.IsNullOrWhiteSpace(appliesTo))
{
// TODO : data is empty, show some error message
return;
}
actasUrl = ThisForm.GetSessionData("ACTAS_URL");
if(string.IsNullOrWhiteSpace(actasUrl))
{
// TODO : data is empty, show some error message
return;
}
if(string.IsNullOrWhiteSpace(ThisForm.ClientSecurityToken)) //access to bootstrapToken
{
// TODO : data is empty, show some error message
return;
}
btoken = Encoding.UTF8.GetString(Convert.FromBase64String(ThisForm.ClientSecurityToken));
}
catch (Exception ex)
{
ShowErrorForm(ex);
}
}
The ActAs Tokes is required when sending a request to GG. The ActAs Token is released from Bootstrap Token. Library Newtonsoft.Json is required and the response is JWT and Newtonsoft.Json can be used to work with it.
The following code can be used to release the ActAs Token:
using Newtonsoft.Json.Linq;
public static string GetActAsToken(string bootstrapToken, string audience, string actasUrl)
{
try
{
var dic = new System.Collections.Specialized.NameValueCollection();
dic.Add("grant_type", "urn:ietf:params:oauth:grant-type:token-exchange");
dic.Add("audience", audience);
dic.Add("subject_token", bootstrapToken);
dic.Add("subject_token_type", "urn:ietf:params:oauth:token-type:jwt");
string actAsToken = null;
using (WebClient client = new WebClient())
{
client.Headers[System.Net.HttpRequestHeader.ContentType] = "application/x-www-form-urlencoded";
var response = client.UploadValues(actasUrl, dic);
var jObject = JObject.Parse(System.Text.Encoding.UTF8.GetString(response));
actAsToken = jObject["access_token"].ToString();
}
return actAsToken;
}
catch (Exception e)
{
throw e;
}
}
It would be better to create a class GGTool in an external library and then place the function GetActAsToken there. The function can be called from the external library in the ND code of the e-Form.
ActAs Token contains number of information, including the date of expiration. The expiration of ActAs Token should be checked before the request is sent to GG. The reson why the validation token is used is to reduce the number of tokens that are being released and consequently reduce the time it takes to release one. The Newtonsoft.Json Library is required, the response is JWT and Newtonsoft.Json can be used to work with it.
using Newtonsoft.Json.Linq;
public static bool ValidateActAsToken(string actAsToken)
{
try
{
if (string.IsNullOrWhiteSpace(actAsToken))
return false;
var parts = actAsToken.Split('.');
string partToConvert = parts[1];
partToConvert = partToConvert.Replace('-', '+');
partToConvert = partToConvert.Replace('_', '/');
switch (partToConvert.Length % 4)
{
case 0:
break;
case 2:
partToConvert += "==";
break;
case 3:
partToConvert += "=";
break;
}
var partAsBytes = Convert.FromBase64String(partToConvert);
var partAsUTF8String = Encoding.UTF8.GetString(partAsBytes, 0, partAsBytes.Count());
var jwtToken = JObject.Parse(partAsUTF8String);
var exp = jwtToken["exp"].Value();
var expDateTime = new DateTime(1970, 1, 1, 0, 0, 0).AddSeconds(exp);
return DateTime.UtcNow < expDateTime;
}
catch (Exception e)
{
throw e;
}
return false;
}
It would be better to create a class GGTool in an external library and then place the function GetActAsToken there. The function can be called from the external library in the ND code of the eForm.
Currently, all the credentials and values are available for a request to GG, however the customer message has not been created yet. The message has to be either XML or JSON format because JWT is being used.
The following message has been created only for the purpose of this tutorial. When creating a customer message, you may need to create one specifically for your service.
When creating a new message, it is very importat to use correct values (e.g. Class, @productName, @version, ...)
{
"$schema":"http://www.govtalk.gov.uk/CM/envelope",
"GovTalkMessage":{
"EnvelopeVersion":"2.0",
"Header":{
"MessageDetails":{
"Class":"<service class name>",
"Qualifier":"request",
"Function":"submit"
}
},
"Body":{
"$schema":"<request schema>",
"Message":{
"Header":{
"Owner":{
"@productName":"<service name>",
"@version":"<version>"
}
},
"Data":{
"@encrypted":"no",
"@gzip":"no",
"Request":{
"@id":"<request name>",
"RequestDetails":{
"MyMessage":"Tutorial message"
}
}
}
}
}
}
}
The next step will be to send the request to GG. The following function returns a response from GG in JSON format.
public static string SendMessage(string uri, string token, string jsonMsg)
{
var client = new HttpClient();
client.DefaultRequestHeaders.Add("Authorization", token);
var content = new StringContent(jsonMsg, Encoding.UTF8, "application/json");
var result = client.PostAsync(uri, content).Result;
string jsonRes = result.Content.ReadAsStringAsync().Result;
return jsonRes;
}
It would be better to create a class GGTool in an external library and then place the function SendMessage there. The function can be called from the external library in the ND code of the eForm.
This article describes how to send a request from e-Forms to GG.
The next step will be to call the SendMessage function and the user will receive a response from GG.
The source code of the extrenal library can be found here.
The library contains code for conversion of GG XML message to JSON and back, codes for reading values from BootstrapToken and many more.
An e-Forms project can be found here.
using System;
using System.Collections.Generic;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Media;
using System.Windows.Shapes;
using System.Xml.Linq;
using Neurodot.Forms.Controls;
using Neurodot.Forms.CustomControls.RadioGroup;
using Neurodot.Forms.Render;
using Neurodot.Forms.Render.Interfaces;
using Microsoft.GGTool.Common;
using System.Xml;
using System.Text;
public static partial class FormCodeClass
{
// To find controls in the current visual tree (i.e. the form) use static methods:
// object ThisForm.FindElement(string elementName); or
// T ThisForm.FindElement<T>(string elementName);
private static readonly string xmlTemplate = @"
<Message xmlns='#your namespace#'>
<Header>
<Owner productName='#product name#' />
</Header>
<Data encrypted = 'no' gzip = 'no'>
<Request id='#request id#'>
<RequestDetails>
<MyMsg></MyMsg>
</RequestDetails>
</Request>
</Data>
</Message>".Replace('\'', '"');
private static string submitUrl = "";
private static string appliesTo = "";
private static string actasUrl = "";
private static string btoken = "";
private static string actAsToken = null;
public static void Loaded(object sender, System.Windows.RoutedEventArgs e)
{
try
{
submitUrl = ThisForm.GetSessionData("SUBMIT_URL");
if(string.IsNullOrWhiteSpace(submitUrl))
{
// TODO : data is empty, show some error message
return;
}
appliesTo = ThisForm.GetSessionData("APPLIES_TO");
if(string.IsNullOrWhiteSpace(appliesTo))
{
// TODO : data is empty, show some error message
return;
}
actasUrl = ThisForm.GetSessionData("ACTAS_URL");
if(string.IsNullOrWhiteSpace(actasUrl))
{
// TODO : data is empty, show some error message
return;
}
if(string.IsNullOrWhiteSpace(ThisForm.ClientSecurityToken)) //access to bootstrapToken
{
// TODO : data is empty, show some error message
return;
}
btoken = Encoding.UTF8.GetString(Convert.FromBase64String(ThisForm.ClientSecurityToken));
}
catch (Exception ex)
{
ShowErrorForm(ex.Message);
}
}
private static bool SubmitRequest(string myMsg, out XmlDocument xmlRespose)
{
var xmlDocTemplate = new XmlDocument();
xmlDocTemplate.LoadXml(xmlTemplate);
xmlRespose = new XmlDocument();
try
{
var node = xmlDocTemplate.GetElementsByTagName("MyMsg");
node[0].InnerText = myMsg;
string jsonData = GGTemplate.GetJsonGGMessage(xmlDocTemplate);
xmlRespose = GGTool.SendMessage(submitUrl, actAsToken, jsonData);
//TODO: here could be validation of the respose
return true;
}
catch (GGException ge)
{
ShowErrorForm(ge.Message);
}
catch (Exception e)
{
ShowErrorForm(e.Message);
}
return false;
}
public static void Button1_Click(object sender, System.Windows.RoutedEventArgs e)
{
if(!GetActAsToken())
{
ShowErrorForm("ActAsToken is null");
return;
}
var xmlResponse = new XmlDocument();
SubmitRequest("Test message", out xmlResponse);
//TODO: processing of the response
}
private static bool GetActAsToken()
{
if(!GGTool.ValidateActAsToken(actAsToken))
{
actAsToken = GGTool.GetActAsToken(btoken, appliesTo, actasUrl );
if (actAsToken == null)
{
return false;
}
}
return true;
}
private static void ShowErrorForm(string msg)
{
//TODO: show error message
}
}