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

Getting all the records from a large SharePoint list with CSOM

I have often had problems getting all the rows from a large Sharepoint list.  Admin limits on the number of rows you can return often cause queries to fail.

The fix for this is to make your query return fewer records using a Where clause in your Caml query.  These can be problematic too as they have eccentric syntax, you don’t always know what data values are actually present in the list, what are the internal fieldnames, or whether the query still fall foul of the Admin limits?

The SharePoint ID field can help.  It’s an internal field that increments automatically to give you a row id.  Its always there, has a different value for each row, & its a simple number – so its perfect for splitting your list into manageable chunks.

To process an entire list, I looked for records whose id was between 1 & 1000, & added them to  a C# list I created in memory (&whose structure matched the SharePoint list).  Then I moved onto to the next chunk and so forth until I was done.

To my knowledge Sharepoint doesnt make available the minimum & maximum list values,  so how did I know when I was done?  You’ll recall I started with id range 1 – 1000, then 1001-2000, Chunk 3 was 2001 to 3000.   If a query returned no records , I incremented a counter of queries that had returned nothing ( called zerocount) by 1.   When this counter reached the arbitrary figure of 700,  I exited the loop.

This helped because in my test the ID field didnt start at 1 (I think it was 38,000o or something like that. This helped us move through the empty ranges until we find ranges that do return records. 

The code below starts with a startkey of 1 & an endkey of 1000 & looks for records whose ids fall in that range.   When its done,  it increments both key values by 1000 ( to give 1001 – 2000) and repeats.  To stop a permanent loop, we stop processing after n  queries have returned nothing, since the last one did actually return data.  To do this, I increment a counter if no data is returned.  Otherwise, I reset the counter to zero.  At some point this counter will reach a point, when I decide there’s probably nothing else to process – so I stop the program.  I set this figure to be 700 – that’s 700 queries & 700,000 ID values searched for and not found in SharePoint, before I decide that there’s probably no more data!  The number of records to process at a time & the number of queries with no rows you accept , is for you to decide.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Microsoft.SharePoint.Client;
using System.Net;
using System.Configuration;
using System.Data;
namespace myNameSpace
{
    class Program
    {
//get the base url from app.config      
        static string baseurl = ConfigurationManager.AppSettings["baseURL"];
        static void Main(string[] args)
        {
//Get your start and end id values for the first chunk from app.config 
//In this case start = 1 & end = 1000
            string eval1 = ConfigurationManager.AppSettings["StartKey"];
            string eval2 = ConfigurationManager.AppSettings["EndKey"];
// Keep track of how many queries return no records.
            int zerocount = 0;
            ClientContext ctx = new ClientContext(baseurl);
            List spList = ctx.Web.Lists.GetByTitle("myList");
            clientContext.Load(spList);
            ctx.ExecuteQuery();
//C# list to match the structure of the SharePoint list & to store the data in memory 
            List<MyClass> reslist = new List<MyClass>();
           string caml = "";
           int counter = 0;       
           int ItemCnt = spList.ItemCount;
           if (spList != null && spList.ItemCount > 0)
           {               
               do
               {
// Query where the ID is between the start & end values 
                   CamlQuery camlQuery = new CamlQuery();
                   caml = string.Format(@"<View><Query><Where><And><Geq>
<FieldRef Name='ID' /><Value Type='Counter'>{0}</Value></Geq>
<Leq><FieldRef Name='ID' /><Value Type='Counter'>{1}</Value></Leq></And>
</Where></Query><ViewFields><FieldRef Name='ID' />
<FieldRef Name='myfield1' /><FieldRef Name='myfield2' />
</ViewFields><RowLimit>5000</RowLimit></View>", eval1, eval2);
                   camlQuery.ViewXml = caml;
                   ListItemCollection listItems = spList.GetItems(camlQuery);
                   clientContext.Load(listItems);
                   clientContext.ExecuteQuery();
                   if (listItems.Count == 0)
                   {// If we get no records increment the zerocount parm
                       zerocount++;
                       Console.WriteLine("Range "+eval1 +" - " +eval2 +" no records.  Zero records _ Chunk: " +  zerocount);                   
                   }
                   else
                   {
// Records found 
//Reset zerocount & add each returned row to the C# list of type<MyClass>
                       zerocount = 0;
                       foreach (ListItem it in listItems)
                       {  counter++;                           
                              if (it.FieldValues["myfield1"] != null)
                           { reslist.Add(new MyClass
                               {
// for this example Myclass is comprised 2 fields - myfield1 & myfield2
                                  myfield1= it.FieldValues["myfield1"].toString(),
                                  myfield2= it.FieldValues["myfield2"].oString()
                               });
                           }
                       }
                    }
// Finished processing that chunk. Now increment start & end values
// of the id range to process by 1000
                   eval1 = increment(eval1, 1000);
                   eval2 = increment(eval2, 1000); 
               } while (zerocount < 700);
// I chose to accept 700 consecutive queries where no data is 
// returned before abandoning the loop 

//  Do other stuff with the list          
               }
        }

        public static string increment(string val1, int val2)
        { int tval1 = Convert.ToInt32(val1);
            tval1 += val2;
            return tval1.ToString();
        }    
    }
}

These are the app settings from the app config:

<appSettings>
    <add key="baseUrl" value="http://mySPurl/" />
    <add key="StartKey" value="1" />
    <add key="EndKey" value="1000" />
</appSettings>

& myClass looks like

class myClass
 {
    public string myfield1{ get; set; }
    public string myfield2 { get; set; }
  }

IF I find code to get the maximum & minimum ids I’ll post it here

 

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);
   }
   });