Skip to content

Welcome

Welcome to my blog about the software tools / techniques I have used.

The first posts, were originally hosted in a variety of places,  &  I thought it would be nice to bring them all together.   Its mainly a journal of  some of the things I have worked on, that might be useful to me in the future.  If it helps anyone else, then great.

I have been developing Web Applications with Domino / Lotus Notes since the 90s, mainly in areas where workflow & security are important. Before that I worked on financial systems with Foxpro, Oracle & PL/1.

In parallel with Domino, recently I have focused more on C# .NET, Sharepoint, JQuery , Javascript, CSS  & HTML5 .

Richard

C# Automated Testing Complex object from Web Service

When your  Assert.AreEqual or CollectionAsser.AreEqual fails inspite of both objects containing the same columns & values, you’ll find a lot of complicated advice about overriding the underlying .Equals code for your classes.

This is a much simpler approach which works with any 1 dimensional object containing many fields.  The code tests a restful web service with many parms

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

using Microsoft.VisualStudio.TestTools.UnitTesting;
using System.Net.Http;
using System.Net;

using Newtonsoft.Json;
using System.Reflection;

using myWebServiceApp.Models;
using myWebServiceApp.Controllers;
namespace myWebServiceApp.Controllers.Tests
{
[TestClass()]
public class myWebServiceAppTests
{
string urlbase = "http://localhost:37781/api/myService/function";

[TestMethod()]
public void Test1_Eligible()
{
//Set url to web service
 string url= urlbase+ "?Parm1=A&Parm2=B&Dob=1994-07-02";
// Set object to contain expected results
 myServiceResultsClass DesiredResults = createDesiredResults("1","0","SuccessResult");
//set object to contain actual results from web service
 myServiceResultsClass testresults = runurl(url);
//pass both to ReflectiveEquals function to see if both contain the same columns & values
 bool testequal = ReflectiveEquals(DesiredResults, testresults);
//Pass the result of testequal into Assert
 Assert.AreEqual(testequal, true, "CheckTest1_FoundandEligible Error");
}

[TestMethod()]
public void Test2()
{//Another Test
string url = urlbase + "?Parm1=FAILVAL&Parm2=B&Dob=1994-07-02";
myServiceResultsClass DesiredResults = createDesiredResults("0", "0", "ExpectedFailResult");
myServiceResultsClass testresults = runurl(url);
bool testequal = ReflectiveEquals(DesiredResults, testresults);
Assert.AreEqual(testequal, true, "NinoTest2_NotFound Error");
}


// Private function calls
private myServiceResultsClass runurl(string url)
{//Run web service & parse results into myServiceResultsClass type object
 myServiceResultsClass retval = new myServiceResultsClass();
 using (var wb = new WebClient()){
  string tmpretval = wb.DownloadString(url);
  retval = JsonConvert.DeserializeObject<myServiceResultsClass>(tmpretval);
 }
return retval;
}

private myServiceResultsClass createDesiredResults(string elStat, string errCod, string desc)
{//Code to build expected results object
myServiceResultsClass retval = new myServiceResultsClass();
retval=( new myServiceResultsClass(){EligibilityStatus=elStat,ErrorCode=errCod,Description=desc});
return retval;
}

private bool ReflectiveEquals(object first, object second)
{//Compare two objects that I expect to contain the same column headers & values
// Test they both have values
if (first == null && second == null)
{
 return true;
}
if (first == null || second == null)
{
 return false;
}
Type firstType = first.GetType();
if (second.GetType() != firstType)
{
 return false; // Or throw an exception
}
// Using 1st object as template, compare its columns & values with the equivalent in the 2nd
foreach (PropertyInfo propertyInfo in firstType.GetProperties())
{
 if (propertyInfo.CanRead)
 {
  object firstValue = propertyInfo.GetValue(first, null);
  object secondValue = propertyInfo.GetValue(second, null);
  if (!object.Equals(firstValue, secondValue))
   {
   return false;
   }
  }
 }
return true;
}
}
}

Simple Console App to generate 60k people records as JSON, based on Game of Thrones or Star Wars names

 

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Configuration;
using System.Data;
using System.Data.SqlClient;
using Newtonsoft.Json;

namespace GeneratePeople
{
 class Program
 {
 static void Main(string[] args)
 {
 List<JSONEmployee> emplist = new List<JSONEmployee>();
for (int i = 1; i <= 60000; i++)
 {
 JSONEmployee person = new JSONEmployee();
 emplist.Add(person);
 person = new JSONEmployee();
 }
 var sortedList = emplist.OrderBy(o => o.Last_Name).ToList();
 var JSONFILE = JsonConvert.SerializeObject(sortedList);
System.IO.File.WriteAllText(@"C:\myFolder\EmplistGOT.json", JSONFILE.ToString());
 }
 }

public class JSONEmployee
 {
 // string surnames = "Ackbar|Ameda|Amidala|Antillies|Anujo|Baba|Bandon|Bane|Beezer|Bibble|Binks|Binks|Brandes|Bridger|Calrissian|Childsen|Crumb|Daala|Dameron|Dan|Darklighter|Dofine|Dooku|Dowmeia|Drallig|Dukal|Dulin|DunLa|Eckener|Etima|Evazan|Fett|Fortuna|Ghintee|Gonn|Goren|Gunray|Haako|Hilse|Hudorra|Jace|Jade|Jeng|Joanson|Kee|Keeg|Kenobi|Koon|Lars|Letah|Lobot|Loneozner|Maac|Malak|Malreaux|Mateil|Maul|Melan|Minx|Mothma|Nadon|Nass|Nunb|Okand|Onassi|Ordo|Organa|Porkins|Qiyn|Racine|Ralter|Ren|Retrac|Rue|Schous|Scrambas|Shé|Shif|Sidious|Sing|Sivrak|Skywalker|Smadis|Solo|Stahn|Tarkin|Thrawn|Tonnika|Torsyn|Trooper|Tyranus|Vader|Vander|Vao|Vida|Vimdim|Windu|Zapalo|Mundi|Kryze|Madine|Plagueis|Rebo";
 
// string firstnames = "Ariq|Arleil|Aurra|Ayy|Bail|Bairdon|Baniss|Bib|Biggs|Boba|Bom|Borsk|Boss|Cad|Camie|Canderous|Cariso|Carth|Cellheim|Chad|Cin|Cornelius|Count|Crix|Dack|Dansra|Darth|Daultay|Del|Delva|Diskret|Elyhek|Ezra|Figrin|Graf|Han|Hat|Hela|Hol|Hugo|Jabba|Jango|JarJar|Jek|Jon|Kai|Karoly|Khaat|Ki-adi|Klatuu|Knol|Koth|Koyi|Kylo|Labria|Lak|Lando|Lufta|Luke|Lumas|Lumpawarrump|Lunae|Mace|Mara|Mas|Max|Meeko|Mission|Momaw|Mon|Nien|Nute|Obi-Wan|Owen|Padmé|Pello|Plo|Poe|Ponda|Qui-Gon|Rune|Ryle|Salacious|Sarrissa|Satine|Sayar|Scout|Sel|Senni|Shada|Shann|Shayl|Sheltay|Shmi|Sio|Tarados|Tas|Tundra|Wedge|Whie|Willhuff";

 string surnames = "Baelish|Baratheon|Bolton|Clegane|Drogo|Giantsbane|Greyjoy|Lannister|Mormont|Naharis|Seaworth|Snow|Sparrow|Stark|Targaryen|Tarly|Tyrell";
 string firstnames = "Arya|Brienne of|Bronn|Catelyn|Cersei|Daario|Daenerys|Davos|Ellaria|Gendry|Gilly|High|Jaime|Jaqen|Jeor|Joffrey|Jon|Jorah|Khal|Margaery|Missandei|Ned|Petyr|Ramsay|Robb|Robert|Roose|Samwell|Sandor|Sansa|Shae|Stannis|Talisa|Tyrion|Tywin";

 public string Employee_Number { get; set; }
 public string Last_Name { get; set; }
 public string First_Name { get; set; }
 public string User_ID { get; set; }
 public Random rno { get; set; }
 
 public JSONEmployee()
 {
 this.rno = new Random();
 this.First_Name = this.RandomElement(firstnames);
 this.Last_Name = this.RandomElement(surnames);
 this.Employee_Number = this.RandomNumber(1000, 9999999).ToString();
 var x = this.RandomNumber(1, 99).ToString();
 this.User_ID = this.First_Name.Substring(0, 1) + this.Last_Name.Substring(0, 3) + x ;
}

public string RandomElement(string rawdata)
 {
 string[] arraydata = rawdata.Split('|');
 Random r = new Random(Guid.NewGuid().GetHashCode());
 int element = r.Next(0, arraydata.Length - 1);
 return arraydata[element];
 }

public int RandomNumber(int from, int to)
 {
  Random r = new Random(Guid.NewGuid().GetHashCode());
  int retval = r.Next(from, to);
  return retval;
 }
}
}

Realtek HD Audio Voices Quiet Windows 7

Quite or underwater vocals on a stereo laptop, without any 5.1 audio

Another possible fix for this issue

 

1) In Control Panel, go to SOUND, not REALTEK HD AUDIO MANAGER

2) Double click the current set of speakers or click properties

3) Click the Enhancements tab

4) Ensure VOICE CANCELLATION, & PITCH SHIFT are  NOT checked

5) Ensure DISABLE ALL SOUND EFFECTS, & IMMEDIATE MODE ARE checked

Case Insensitive QueryString in Angular

If your Angular app is expecting a QueryString parameter of myParm1,  &  instead it gets passed MypArM1, then the following command will fail:

var MyParmOne_Value = $location.search.myParm1 ; 

This JS code ( in the angular controller) deals with that by building a new array from the query_string & forcing every key to lowercase.  This new array is then used to retrieve values instead of the $location.search object:

var QSarr = [], hash;
var sourceQS = window.location.href.slice(window.location.href.indexOf('?') + 1).split('&');
for (var i = 0; i <sourceQS.length; i++) {
 hash =sourceQS[i].split('=');
 var key = hash[0].toLowerCase()
 QSarr.push(key);
 QSarr[key] = hash[1];
 }
...... 
//later we want that QueryString value so we search for myparm1 in the array
 var MyParmOne_Value =QSarr["myparm1"];

 

 

 

When Your C# deserialize fails

I had a problem deserializing some real world XML into classes created by doing an Edit \ Paste Special \ Paste XML as classes in Visual Studio 2012.

My Application (MyAPP )consumes numerous SOAP services provided by an external organisation (xtOrg) , & most work with the following code:

var responseXML = getXMLSOAPResponse(credential,url, SOAPObj);
var reader = new StringReader(responseXML.ToString());
XmlSerializer ser = new XmlSerializer(typeof(Envelope));
var instance = (Envelope)ser.Deserialize(reader);

In one service, the deserialize failed with the error “specified type was not recognized: name= PersonNameType” (See the bottom of the page for the XML)  This was frustrating because I only needed a subset of the data, a list of ‘OtherReferences’, which as far as I could see were parsing OK, before the failing data.
The ASP &  MSDN forums were full of people that offered solutions, but I could not get any of their suggestions to work, so I addressed it manually, using XPATH manual parsing.

All I wanted from this were the OtherRef fields, I could manually parse them with Xpath. This might not be suitable for a complete deserialization of the XML, but it suited the simple data I wanted : a list of other references:

List<OtherRef> retval = new List<OtherRef>();
XmlDocument xmldoc = new XmlDocument();

var responseXML = getXMLSOAPResponse(credential,url, SOAPObj);
// getSoapResponse is a custom function to fire a predefined 
// SOAPObject, at a url, with a credential. It returns a string

xmldoc.LoadXml(responseVal);
// retrieve XML & load to XMLDOC

XmlNamespaceManager nsmgr = new XmlNamespaceManager(xmldoc.NameTable);
nsmgr.AddNamespace("ns4", "http://www.xtOrg.co.uk/frameworki/common");
//Create a namespace manager to handle the namesspaces we want within the 
//XML. ( I suspect that this was part of the problem in my original code )


XmlNodeList nodelist = xmldoc.SelectNodes("//ns4:OtherRef", nsmgr);
// get the otherrefs Node into a nodelist. 
// This is the tricky part, and is an iterative process, 
//depending on the degree of nesting you want 

foreach (XmlNode node in nodelist)
{
XmlNodeList Childnodelist = node.ChildNodes;
// Build another Nodelist for the current Node
string OtherRefValue = "";
string OtherRefType = "";
// Set up empty fields for the object we want to build: a 2 column list

 foreach (XmlNode Chnode in Childnodelist)
 {
// parse the type & value into the correct temporary variable type 
 if (Chnode.Name == "ns4:OtherRefValue")
 {
 OtherRefValue = Chnode.InnerText; 
 }

if (Chnode.Name == "ns4:OtherRefType")
 {
 OtherRefType = Chnode.InnerText; 
 }
 }

 OtherRef tempRef = new OtherRef();
//Create a strongly typed OtherRef object 

 tempRef.OtherRefType = OtherRefType;
 tempRef.OtherRefValue = OtherRefValue;
// and populate it 

 retval.Add(tempRef);
// and add it to the list 
}

Then you get a strongly typed list.  Its not a solution for every scenario, especially if you wanted to create  a POCO object of the entire XML.

One other gotcha with Edit \ Paste Special \ Paste XML as classes, is that sometimes number objects can be defined to be the smallest available ,  Eg uint which in sometimes can only store a value  of 65,535….  Suppose your service retrieves a value of 100000…

Here is the XML That was returned.  The green text shows the data I wanted & the red shows the data that was causing the problem

<env:Envelope xmlns:env="http://schemas.xmlsoap.org/soap/envelope/">
 <env:Header>
 <wsa:RelatesTo xmlns:wsa="http://www.w3.org/2005/08/addressing">0b032838-d2de-db86-a3c2-nnnnnnnn</wsa:RelatesTo>
 </env:Header>
 <env:Body>
 <ns6:GetPersonDetailsResponse xmlns:ns3="http://www.govtalk.gov.uk/people/PersonDescriptives" xmlns:ns4="http://www.xtOrg.co.uk/api/common" xmlns:ns5="http://www.govtalk.gov.uk/people/AddressAndPersonalDetails" xmlns:ns6="http://www.xtOrg.co.uk/api/getpersondetails" xmlns:ns7="http://www.xtOrg.co.uk/api/searchperson" xmlns:ns8="http://www.xtOrg.co.uk/api/createpersonepisode" xmlns:ns9="http://www.xtOrg.co.uk/api/createperson" xmlns:ns10="http://www.xtOrg.co.uk/api/CreateEp" xmlns:ns11="http://www.xtOrg.co.uk/api/createform">
 <PersonDetails>
 <apiPersonId>99999</apiPersonId>
 <PersonNames>
 <ns4:PersonName>
 <ns3:PersonGivenName>Bruce</ns3:PersonGivenName>
 <ns3:PersonFamilyName>Skywalker</ns3:PersonFamilyName>
 <ns4:PersonNameType>MAIN</ns4:PersonNameType>
 </ns4:PersonName>
 </PersonNames>
 <PersonAddresses>
 <ns4:PersonAddress CorrespondenceAddress="true" StartDate="2015-06-26">
 <ns4:PostalAddress>
 <ns5:Line>22 Acacia Ave</ns5:Line>
 <ns5:Line>AnyTown</ns5:Line>
 </ns4:PostalAddress>
 <ns4:PersonAddressType>MAIN</ns4:PersonAddressType>
 <ns4:AddressInLA>N</ns4:AddressInLA>
 <ns4:Accommodation>
 <ns4:DwellingType>MAIN</ns4:DwellingType>
 </ns4:Accommodation>
 </ns4:PersonAddress>
 </PersonAddresses>
 <PersonBirthDate EstimatedDateOfBirth="false">1913-06-11</PersonBirthDate>
 <PersonContact>
 <ns4:Telephone TelUse="home" TelMobile="no" TelPreferred="no">
 <ns5:TelNationalNumber>01234567890</ns5:TelNationalNumber>
 </ns4:Telephone>
 </PersonContact>
 <OtherRefs>
 <ns4:OtherRef>
 <ns4:OtherRefValue>9999999999</ns4:OtherRefValue>
 <ns4:OtherRefType>NHS</ns4:OtherRefType>
 </ns4:OtherRef>
 <ns4:OtherRef>
 <ns4:OtherRefValue>123456</ns4:OtherRefValue>
 <ns4:OtherRefType>PRN</ns4:OtherRefType>
 </ns4:OtherRef>
 <ns4:OtherRef>
 <ns4:OtherRefValue>P123456</ns4:OtherRefValue>
 <ns4:OtherRefType>LEGACY_PERSON_ID</ns4:OtherRefType>
 </ns4:OtherRef>
 </OtherRefs>

 <Gender>1</Gender>
 <MaritalStatus>n</MaritalStatus>
 <InvolvedWorkers>
 <ns4:Worker>
 <ns4:apiWorkerId>100137910</ns4:apiWorkerId>
 <ns4:WorkerName xsi:type="ns4:PersonNameType" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
 <ns3:PersonGivenName>Joe</ns3:PersonGivenName>
 <ns3:PersonFamilyName>Bloggs</ns3:PersonFamilyName>
 <ns4:PersonNameType>MAIN</ns4:PersonNameType>
 </ns4:WorkerName>
 <ns4:WorkerContact>
 <ns4:Telephone TelMobile="yes" TelPreferred="no">
 <ns5:TelNationalNumber>07976282585</ns5:TelNationalNumber>
 </ns4:Telephone>
 </ns4:WorkerContact>
 </ns4:Worker>
 <ns4:Worker>
 <ns4:apiWorkerId>100137914</ns4:apiWorkerId>
 <ns4:WorkerName xsi:type="ns4:PersonNameType" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">

 <ns3:PersonGivenName>Fred</ns3:PersonGivenName>
 <ns3:PersonFamilyName>Smith</ns3:PersonFamilyName>
 <ns4:PersonNameType>MAIN</ns4:PersonNameType>
 </ns4:WorkerName>
 <ns4:WorkerContact>
 <ns4:Telephone TelUse="work" TelMobile="no" TelPreferred="no">
 <ns5:TelNationalNumber>01926785623</ns5:TelNationalNumber>
 </ns4:Telephone>
 <ns4:Telephone TelMobile="yes" TelPreferred="no">
 <ns5:TelNationalNumber>0778523689</ns5:TelNationalNumber>
 </ns4:Telephone>
 </ns4:WorkerContact>
 </ns4:Worker>
 </InvolvedWorkers>
 </PersonDetails>
 </ns6:GetPersonDetailsResponse>
 </env:Body>
</env:Envelope>

Jquery Datepicker with onclick remote validation

Step 1 define field in Model with the System.Web.MVC.remoteattribute

 [Remote("isDateAvailable", "Validation")]
 [DisplayName("Appointment Date")]
 public DateTime AppointmentOn { get; set; }
This tells the system to do server side validation in the client,  & look in ValidationController.cs for a function called isDateAvailable,  & return the validation as a JSON object to the client.

Step 2 define   isDateAvailable in ValidationController.cs

public JsonResult isDateAvailable(string AppointmentOn )
{
   string Message = "";
   if (AppointmentOn  != null)
            {
 // Has a valid date been passed
                DateTime dt = new DateTime();
                try
                {
                    dt = Convert.ToDateTime(AppointmentOn);
                }
                catch (Exception e)
                {
                    Message = "Invalid Date";
                    return Json(Message, JsonRequestBehavior.AllowGet);
                }
//If we have reached here date is a valid format  - so now do the validation required
                if (PerformMyCustomTestonDate(dt))
                {
// test was passed
                    return Json(true, JsonRequestBehavior.AllowGet);
                }
                else
                {
//  return false test failed 
                    Message = "The date you requested is not available";
                    return Json(Message, JsonRequestBehavior.AllowGet);
                }
            }
            else
            {
                Message = "Date cannot be EMPTY";
                return Json(Message, JsonRequestBehavior.AllowGet);
            }
return Json(Message, JsonRequestBehavior.AllowGet);
}

Step 3 Wire up jquery ui date picker with the validation

$('#EpisodeReceived').datepicker({
 changeMonth: true,
 changeYear: true,
 dateFormat: "dd/mm/yy",
 onSelect: function (dateText, inst) {
// On select workout what element is being clicked & get the form
                var id ="#"+ $(this).attr('id');
                var Aform = $(id).closest('form');
// Tell the form to validate this field
                var validator = $(Aform).validate();
                var thisValidate = validator.element(id);
   }
   });

 

 

Angular JS filter array by date & jquery date picker

Step 1 Add Date picker filter fields from & to to form decorated with angular ng tags to HTML page

From Date:<input type="text" ng-model="fromDate" id="from" ng-click="First()">
To Date:<input type="text" ng-model="toDate" id="to" ng-click="First()">

When clicked into it runs the “First()” function that takes you to the first row in the array

Step 2 Jquery Date picker for the Fields with ids = “from” & “to”   to JQUERY $ function

$(function () {
 $("#from").datepicker({
 changeMonth: true,
 changeYear: true,
 dateFormat: "dd/mm/yy",
 onClose: function (selectedDate) {
 $("#to").datepicker("option", "minDate", selectedDate);
 }});
 $("#to").datepicker({
 changeMonth: true,
 changeYear: true,
 dateFormat: "dd/mm/yy",
 onClose: function (selectedDate) {
 $("#from").datepicker("option", "maxDate", selectedDate);
 }});
 });

Step 3 Set up ng-repeat loop with filter on the HTML Page:

......   other table html
 <tr ng-repeat ="item in filtered = ( rows | filter:otherTextfilter | dateFilter:fromDate:'from' | dateFilter:toDate:'to') | .... other filters / sort etc...
 <td>{{ item.field1}} </td> <td>{{ item.PersonBirthDate}} </td>   etc....
 </tr>

Step 4 Define dateFilter  in the Angular js controller:

myApp.filter('dateFilter', function () {
return function (items, parmDate, mode) {
// function reads in the items array, 
//the value of the parameter date field, 
//& whether this is a greater than or less than comparison.
//  If this is initialising or there are no parameters, return the list unfiltered
if (parmDate === undefined) {
 return items;
 }
// If the parmDate is not yet in the format dd/mm/yyyy - return it unfiltered
if (parmDate.length < 8) {
 return items;
 }
// we must have a valid parmdate to to have reached here.
mode = mode.toLowerCase();
 var dateArr = parmDate.split("/");
 var  reccnt = 0;
 try{
 // Convert the parameter date string into a date.  
//This assumes dd/mm/yyyy - the format set in the jquery date picker in step 2
var filtered = [];
var parmDateDTE = new Date(dateArr[2], dateArr[1] - 1, dateArr[0]);
// adjust parmDateDte by 1 day to allow for cleaner greater than or less than
// tests.  GE or LE tests are messy due to time components
if (mode == "from") {
 parmDateDTE.setDate(parmDateDTE.getDate() - 1);
 }
if (mode == "to") {
 parmDateDTE.setDate(parmDateDTE.getDate() + 1);
 }
angular.forEach(items, function (item) {
// create a date field for each item in the array. Redefine it each time so that if
//the item field is missing, itemDateVal is set to today's date .
 //A null would trigger an error .  But you could set itemDateVal to anything 
var itemDateVal = new Date();
 if (item.PersonBirthDate) {
 itemDateVal = new Date(item.PersonBirthDate.Value);
 }
 // Push the item to the filtered list if it matches the criteria. 
if (mode == "from") {
 if (itemDateVal > parmDateDTE) {
 filtered.push(item);
 reccnt++;
 } }
if (mode == "to") {
 if (itemDateVal < parmDateDTE) {
 filtered.push(item);
 reccnt++; 
}}
 });
return filtered;
 }
 catch (err){
 alert(err.message);
 }};
});
First goes in the main controller

$scope.First = function (value) {
 $scope.startFrom = 0;
 $scope.goTo = $scope.pageSize;
 $scope.pageNo = getPageNo();
 return $scope.startFrom;
 }