Sabre API Updates For PCI TLS v1.2 + High Availability URLs

I’ve been receiving email from Sabre regarding Payment Card Industry (PCI) TLS v1.2 security deadlines and Sabre’s High Availability (HA) infrastructure. (Sabre has introduced new URLs for use with Sabre APIs).

I’ve navigated a maze of URLs and PDFs from Sabre. The majority of these were not helpful to me but I muddled through and reduced the upgrade process to a few steps. Note that these steps handle both the high availability URL updates and the TLS v1.2 updates.

The steps below are accomplished using Visual Studio Professional 2017. If you’re not using it yet, you should be.

Step 1 – Update to use the High Availability URLs

In VS, Find and Replace in the entire solution (*.cs files only)
From this : https://webservices.sabre.com
To this : https://webservices.havail.sabre.com

At this point, I tried to run against the new HA URLs .. but no luck, here’s the error:
Exception Message : The request was aborted: Could not create SSL/TLS secure channel.

Step 2 – Update to the .NET Framework 4.6.1

I updated ALL of my projects to .NET Framework 4.6.1, this handles the TLS v1.2 upgrade.

Step 3 – Recompile and Run

Nothing else to do, I’m running now with the new HA URLs and also using TLS v1.2.

Where’s my seat?

Summary: Sabre TravelItineraryReadService TravelItineraryReadRQ version 2.2.0 returns incomplete Seat data. TravelItineraryReadRQ version 3.4.0 returns all of the Seat data.

I had bug in my PNR capture code – it was throwing an exception on my persist flight details stored procedure call because I wasn’t giving it a required seat number. It was a trivial code fix, I needed to pass an empty string to the stored procedure (not a null value).

But did I have a bug further upstream in my TravelItineraryReadRS parsing and mapping code? Or is the data really missing?

My current version of the TravelItineraryReadService is using TravelItineraryReadRQ version 2.2.0. Below is the snippet from the TravelItineraryReadRS. You can see that it’s only returning a seat number for segment 2, all of the other segments have no value for the seat number – the attribute is missing entirely. So I don’t have any parsing/mapping issues, sabre isn’t giving me the data.

<TravelItineraryReadRS Version="2.2.0">
...
  <Seats>
	<Seat NameNumber="01.01" SegmentNumber="0001" Status="HRQ">
	  <FlightSegment>
		<DestinationLocation LocationCode="PHX" />
		<OriginLocation LocationCode="SBA" />
	  </FlightSegment>
	</Seat>
	<Seat Changed="N" NameNumber="01.01" Number="04A" SegmentNumber="0002" SegmentStatus="HK" SmokingPreference="N" Status="HRS" TypeTwo="WLMI">
	  <FlightSegment>
		<DestinationLocation LocationCode="ORD" />
		<OriginLocation LocationCode="PHX" />
	  </FlightSegment>
	</Seat>
	<Seat NameNumber="01.01" SegmentNumber="0003" Status="HRQ">
	  <FlightSegment>
		<DestinationLocation LocationCode="PHX" />
		<OriginLocation LocationCode="ORD" />
	  </FlightSegment>
	</Seat>
	<Seat NameNumber="01.01" SegmentNumber="0004" Status="HRQ">
	  <FlightSegment>
		<DestinationLocation LocationCode="SBA" />
		<OriginLocation LocationCode="PHX" />
	  </FlightSegment>
	</Seat>
  </Seats>
...
</TravelItineraryReadRS>

I’m in the middle of evaluating (and implementing) TravelItineraryReadRQ version 3.4.0. Cool, it has my seat data! Maybe I need to roll this into production sooner than planned…

<TravelItineraryReadRS Version="3.4.0">
...
  <Seats>
	<Seat Changed="N" NameNumber="01.01" Number="02A" SegmentNumber="0001" SegmentStatus="PN" SmokingPreference="N" Status="HRS" TypeTwo="">
	  <FlightSegment>
		<DestinationLocation LocationCode="PHX" />
		<OriginLocation LocationCode="SBA" />
	  </FlightSegment>
	</Seat>
	<Seat Changed="N" NameNumber="01.01" Number="04A" SegmentNumber="0002" SegmentStatus="HK" SmokingPreference="N" Status="HRS" TypeTwo="WLMI">
	  <FlightSegment>
		<DestinationLocation LocationCode="ORD" />
		<OriginLocation LocationCode="PHX" />
	  </FlightSegment>
	</Seat>
	<Seat Changed="N" NameNumber="01.01" Number="03A" SegmentNumber="0003" SegmentStatus="PN" SmokingPreference="N" Status="HRS" TypeTwo="">
	  <FlightSegment>
		<DestinationLocation LocationCode="PHX" />
		<OriginLocation LocationCode="ORD" />
	  </FlightSegment>
	</Seat>
	<Seat Changed="N" NameNumber="01.01" Number="03F" SegmentNumber="0004" SegmentStatus="PN" SmokingPreference="N" Status="HRS" TypeTwo="">
	  <FlightSegment>
		<DestinationLocation LocationCode="SBA" />
		<OriginLocation LocationCode="PHX" />
	  </FlightSegment>
	</Seat>
  </Seats>
...
</TravelItineraryReadRS>

Singleton Application With NotifyIcon

I recently had to implement 2 changes to a winforms application I’m working on, here’s the general requirements:

  • Can only have 1 instance of the application running – make it a singleton application
  • The user can close (hide) the application but it remains running until exited – utilize the notify icon (tray icon)

I searched google and found a bunch of helpful sources for both, this is a melding of several of them to fit my needs.

Here are the guts of the winform that deals with the notifyIcon:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
using System;
using System.Windows.Forms;
 
namespace SingletonAppWithNotifyIcon
{
    public partial class MainForm2 : Form
    {
        private bool IsQuit { get; set; }
 
        public MainForm2()
        {
            InitializeComponent();
        }
 
        private void MyFormClosing(object sender, FormClosingEventArgs e)
        {
            if (IsQuit)
                return;
 
            HideMe();
            e.Cancel = true;
        }
 
        private void HideMe()
        {
            Hide();
        }
 
        public void ShowMe()
        {
            WindowState = FormWindowState.Normal;
            Visible = true;
            Activate();
        }
 
        private void QuitMe()
        {
            IsQuit = true;
            Application.Exit();
        }
 
        private void MainForm2_Load(object sender, EventArgs e)
        {
            notifyIcon1.ContextMenu = new ContextMenu();
            notifyIcon1.ContextMenu.MenuItems.Add(new MenuItem("Quit My Fancy App", NotifyIconQuit));
        }
 
        private void NotifyIconQuit(object sender, EventArgs e)
        {
            QuitMe();
        }
 
        private void NotifyClick(object sender, EventArgs e)
        {
            ShowMe();
        }
 
        private void CloseClick(object sender, EventArgs e)
        {
            HideMe();
        }
 
        private void QuitClick(object sender, EventArgs e)
        {
            QuitMe();
        }
    }
}

Here the highlights for the singleton application:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
using System;
using System.Windows.Forms;
using Microsoft.VisualBasic.ApplicationServices;
 
namespace SingletonAppWithNotifyIcon
{
    static class Program
    {
        [STAThread]
        static void Main()
        {
            Application.EnableVisualStyles();
            Application.SetCompatibleTextRenderingDefault(false);
 
            SingleInstanceAppStarter.Start(new MainForm2(), StartNewInstance);
        }
 
        static void StartNewInstance(object sender, StartupNextInstanceEventArgs e)
        {
            var forms = Application.OpenForms;
            var frm = forms["MainForm2"] as MainForm2;
            if (frm != null)
            {
                frm.ShowMe();
            }
            else
            {
                var f = new MainForm2();
                f.ShowDialog();
            }
        }
    }
 
    class SingleInstanceApp : WindowsFormsApplicationBase
    {
        public SingleInstanceApp() { }
 
        public SingleInstanceApp(Form f)
        {
            IsSingleInstance = true;
            MainForm = f;
        }
    }
 
    public class SingleInstanceAppStarter
    {
        static SingleInstanceApp _app;
 
        public static void Start(Form f, StartupNextInstanceEventHandler handler)
        {
            if (_app == null && f != null)
            {
                _app = new SingleInstanceApp(f);
            }
 
            if (_app == null) return;
 
            _app.StartupNextInstance += handler;
            _app.Run(Environment.GetCommandLineArgs());
        }
    }	
}

Here’s a sample solution (nice and simple) for you to download and experiment with…
SingletonAppWithNotifyIcon.zip

Custom List In app.config File

I recently had the need to define a custom list in an application. There are many ways to make this happen but I wanted it in the app config file.

After a few searches, I pieced together the following example.

The app.config below has a custom section, AppConfigCollection.CustomDictionary. This section is declared in the configSections and the type attribute has my handler class and assembly name.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
 
  <configSections>
    <section name="AppConfigCollection.CustomDictionary" type="AppConfigCollection.CustomDictionary, AppConfigCollection"/>
  </configSections>
 
  <AppConfigCollection.CustomDictionary>
 
    <NFL>
      <AFC>
        <AFC_WEST>
          <Item code="DEN" team="Denver Broncos" />
          <Item code="MCI" team="Kansas City Chiefs" />
          <Item code="SAN" team="San Diego Chargers" />
          <Item code="OAK" team="Oakland Raiders" />
        </AFC_WEST>
      </AFC>
 
      <NFC>
        <NFC_EAST>
          <Item code="DFW" team="Dallas Cowboys" />
          <Item code="PHL" team="Philadelphia Eagles" />
          <Item code="EWR" team="New York Giants" />
          <Item code="IAD" team="Washington Redskins" />
        </NFC_EAST>
      </NFC>
    </NFL>
 
  </AppConfigCollection.CustomDictionary>
 
</configuration>

The handler class CustomDictionary inherits from IConfigurationSectionHandler. This interface has been deprecated but it’s still available and easy to implement and make it work.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
using System;
using System.Collections.Generic;
using System.Configuration;
using System.Linq;
using System.Xml.Linq;
 
namespace AppConfigCollection
{
 
    class Program
    {
        static void Main(string[] args)
        {
            var teamDictionary = (Dictionary<string, string>)ConfigurationManager.GetSection("AppConfigCollection.CustomDictionary");
 
            Console.WriteLine();
            Console.WriteLine("NFL TEAMS");
            foreach (var key in teamDictionary.Keys)
            {
                Console.WriteLine("{0} : {1}", key, teamDictionary[key]);
            }
 
            Console.WriteLine();
            Console.WriteLine("press any key to continue ...");
            Console.ReadLine();
        }
    }
 
    public class CustomDictionary : IConfigurationSectionHandler
    {
        public object Create(object parent, object configContext, System.Xml.XmlNode section)
        {
            var rList = new Dictionary<string, string>();
 
            var doc = XDocument.Parse(section.OuterXml);
            var root = (XElement)doc.FirstNode;
 
            var rootElement = (XElement)root.FirstNode;
            if (rootElement.Name != "NFL")
                throw new ConfigurationErrorsException(
                    "AppConfigCollection.CustomDictionary section only accepts 'NFL' elements.");
 
            try
            {
                foreach (XElement conference in rootElement.Nodes())
                {
                    Console.WriteLine();
                    Console.WriteLine("Conference : {0}", conference.Name);
 
                    foreach (XElement division in conference.Nodes())
                    {
                        Console.WriteLine("  Division : {0}", division.Name);
 
                        var teamList = from div in division.Elements("Item") select div;
                        foreach (var item in teamList)
                        {
                            var code = item.Attribute("code").Value;
                            var team = item.Attribute("team").Value;
 
                            Console.WriteLine("    Team ({0}) : {1}", code, team);
                            rList.Add(code, team);
                        }
                    }
                }
            }
            catch (Exception ex)
            {
                throw new ConfigurationErrorsException(
                    "Error reading element.", ex);
            }
 
            return rList;
        }
    }
 
}

This is the output.
AppConfigCollection

Feel free to download the application solution – experiment and adapt it for your needs.

HTTPS WCF web service web config

Here’s the web config for a WCF web service. I struggled getting it to work under HTTPS. But when all done and working it’s not terrible – I’ll try to highlight the relevant names because that was my primary struggle point. I won’t go into explaining everything, there are tons of posts out there can (and do) explain much better than I can or want to here.

WebConfig

I pieced together several different examples in an attempt to get this working and at first my highlighted names didn’t match – and no luck working. So here are the highlighted sections of the serviceModel element with the names matching up.

See ServiceBehaviorA, wsHttpBinding, and TransportSecurity. These are referenced in the endpoint and must also be referenced in the behaviors and bindings elements.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
  <system.serviceModel>
 
    <services>
      <service name="S2C.API.Service" 
 
               <!-- SEE BELOW in serviceBehaviors -->
               behaviorConfiguration="ServiceBehaviorA">
 
        <endpoint address="" 
 
                  <!-- SEE BELOW in bindings -->
                  binding="wsHttpBinding"
 
                  <!-- SEE BELOW the binding name -->
                  bindingConfiguration="TransportSecurity"
 
                  contract="S2C.API.IService" />
 
        <endpoint address="mex" 
                  binding="mexHttpBinding" 
                  contract="IMetadataExchange" />
      </service>
    </services>
 
    <behaviors>
      <serviceBehaviors>
        <behavior name="ServiceBehaviorA">
 
          <!-- To avoid disclosing metadata information, 
               set the value below to false and remove 
               the metadata endpoint above before deployment -->
          <serviceMetadata httpGetEnabled="true" httpsGetEnabled="true" />
 
          <!-- To receive exception details in faults for debugging purposes, 
               set the value below to true. Set to false before deployment 
               to avoid disclosing exception information -->
          <serviceDebug includeExceptionDetailInFaults="true"/>
 
        </behavior>
      </serviceBehaviors>
    </behaviors>
 
    <bindings>
      <wsHttpBinding>
        <binding name="TransportSecurity">
 
          <security mode="Transport">
            <transport clientCredentialType="None"/>
          </security>
 
        </binding>
      </wsHttpBinding>
    </bindings>
 
    <serviceHostingEnvironment multipleSiteBindingsEnabled="true" />
 
  </system.serviceModel>

Solution for my bad dates

Following up my previous post “I have bad dates“, here’s my solution. I may have to tweak this some at the end of the year but this seems to be working for now.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
//
public static DateTime wsGetDate(string badSabreDate, DateTime srcDT)
{
    // force the bad sabre date into a leap year
    var leapYear = "2000-" + badSabreDate;
 
    var year = srcDT.Year;
    DateTime dt = DateTime.Now.AddYears(-1);
    if (DateTime.TryParse(leapYear, out dt))
    {
	if (dt.Month < srcDT.Month)
	{
	    // parsed month is less than srcDT, increment my year
	    year++;
	}
 
	return new DateTime(year, dt.Month, dt.Day, dt.Hour, dt.Minute, dt.Second);
    }
 
    var msg2 = string.Format("Can't parse {0} into a valid DateTime, {1}", badSabreDate, leapYear);
    throw new ApplicationException(msg2);
}
//

And here’s a piece of the car segment parsing code that calls the new method:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
//
if (_Resp.TravelItinerary.ItineraryInfo.ReservationItems[i].Vehicle != null)
{
    #region CAR
    var car = _Resp.TravelItinerary.ItineraryInfo.ReservationItems[i].Vehicle;
    Log.Debug("Car --------------------------------------------------");
    string confID = "";
    if (car.ConfirmationNumber != null) confID = SabreWS.wsString(car.ConfirmationNumber);
 
    int RPHv = SabreWS.wsInt(car.SegmentNumber);
    string statusv = SabreWS.wsString(car.Status);
    string ID = "";
    if (car.POS != null) ID = SabreWS.wsString(car.POS.Source.RequestorID);
 
    Log.Debug(RPHv + statusv.PadLeft(4) + " - " + " requestorID: " + ID);
 
    string PU = SabreWS.wsString(car.VehRentalCore.LocationDetails.LocationCode);
    tmpForeignSts = AirportLookupCache.GetForeignStatus(PU);
    ForeignSts = tmpForeignSts > ForeignSts ? tmpForeignSts : ForeignSts;
 
    DateTime PUDT = wsGetDate(car.VehRentalCore.PickUpDateTime, DateTime.Now);
    Log.Debug("Pickup1: " + PU.PadRight(5) + PUDT.ToString());
 
    string RT = "";
    if (car.VehRentalCore.DropOffLocationDetails != null) RT = SabreWS.wsString(car.VehRentalCore.DropOffLocationDetails.LocationCode);
    if (RT.Length == 0) RT = PU;
    tmpForeignSts = AirportLookupCache.GetForeignStatus(RT);
    ForeignSts = tmpForeignSts > ForeignSts ? tmpForeignSts : ForeignSts;
 
    DateTime RTDT = PUDT;
    try
    {
	RTDT = wsGetDate(car.VehRentalCore.ReturnDateTime, PUDT);
    }
    catch (Exception a)
    {
	a.Message.ToString();
	Log.Error(car.VehRentalCore.ReturnDateTime + " --> error parsing this as the Return DateTime, forcing to PUDT");
    }
...
//

I have bad dates

Some back story … I use Sabre web services to read and extract travel data into a database. The data is used for a suite of travel related products and applications. If you’ve worked on travel applications, you know that it’s some wild west programming – data consistency is awful.

Web services was supposed to fix everything because now it’s structured data, no more screen scraping! Well, it is better than it was in the past but there are still problems.

Sabre web services has an odd bug that only strikes on the last day of the month AND your car/hotel segment starts on that day. See the PickUpDateTime below – it should be 2013 not 2014. (Notice that they have the ReturnDateTime correct.)

1
2
3
4
5
6
7
8
<OTA_TravelItineraryRS Version="2003A.TsabreXML1.15.1">
. . .
    <VehRentalCore PickUpDateTime="2014-07-31T16:30:00" PickUpDay="3" ReturnDateTime="2013-08-02T16:00:00">
      <PickUpLocation LocationCode="ATL" CodeContext="IATA" />
      <ReturnLocation LocationCode="JAX" CodeContext="IATA" />
    </VehRentalCore>
. . .
</OTA_TravelItineraryRS>

Since I’m running an older version of the TravelItinerary web service, thought I’d try the latest and greatest version available to see if the problem is fixed. As you can see below, Sabre’s solution is to drop the year. Nice. Are you kidding me? We went from the wrong date to an invalid ISO format across the board?

1
2
3
4
5
6
7
8
<TravelItineraryReadRS Version="2.2.0">
. . .
    <VehRentalCore PickUpDateTime="07-31T16:30" PickUpDay="3" ReturnDateTime="08-02T16:00">
      <DropOffLocationDetails LocationCode="JAX" />
      <LocationDetails LocationCode="ATL" />
    </VehRentalCore>
. . .
</TravelItineraryReadRS>

…I’d love to hear the reasoning behind that solution hack. Why is there such a lack of quality these days? I have some stories I want to post about this later…

Now I have to write new logic to decode this crap which will replace the old logic I had to fix the original crap.

Logging Serializable Objects

Here’s a handy method to log your object serialized as XML

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
//
public void LogObject(object obj)
{
	var msg = "";
	try
	{
		using (var w = new StringWriter())
		{
			var s = new XmlSerializer(obj.GetType());
			s.Serialize(w, obj);
			w.Flush();
			msg = w.ToString();
		}
	}
	catch (Exception e)
	{
		msg = e.Message.ToString();
	}
 
	msg = string.Format("{0}\n{1}", obj.GetType(), msg);
	Log.Debug(msg);
}
 
LogObject(msgHeader);
//

This is what my logger spit out

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
OTA_TravelItineraryReadNS2_2_0.MessageHeader
<?xml version="1.0" encoding="utf-16"?>
<MessageHeader xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns="http://www.ebxml.org/namespaces/messageHeader">
  <From>
    <PartyId>55555</PartyId>
  </From>
  <To>
    <PartyId>789789789</PartyId>
  </To>
  <CPAId>2WE2</CPAId>
  <ConversationId>130615-120130.2844</ConversationId>
  <Action>TravelItineraryReadLLSRQ</Action>
  <MessageData>
    <MessageId>mid:200051209-18808-23933@clientofsabre.com</MessageId>
  </MessageData>
</MessageHeader>

Frustration …

Frustrated with Sabre web services, grrrrr!

The version of the TravelItineraryRead web service I’m using is “2003A.TsabreXML1.15.1”. Yes, I know its a bit dated but it fits the needs .. until recently. I’m seeing more Rail segments come across and this version does not support them. So, I wanted to test the latest TravelItineraryRead version to see how or if Rail is supported. 

So I built my proxy classes using 
wsdl.exe  http://webservices.sabre.com/wsdl/tpfc/TravelItineraryReadLLS2.2.0RQ.wsdl  /namespace:OTA_TravelItineraryReadNS2_2_0
And setup my code to call the new service  

1
2
3
4
5
. . .
var serviceObj = new TravelItineraryReadService();
serviceObj.MessageHeaderValue = msgHeader;
serviceObj.Security = security;
TravelItineraryReadRS response = serviceObj.TravelItineraryReadRQ(req);

… and I get a null response. No errors or exceptions just a null response. So, I tweak my objects and try again – same null response. Make more changes – same null response. I go through several cycles of this and I’m stuck, I need help – another set of eyes to see what I’m doing wrong. (BTW, Sabre’s documentation on this web service is horrible. This is the root of my problem)

Next I contact Sabre’s web services support through email. This is my frustration … The email conversation goes on for 6 days. Maybe their support group is outside the USA or their policy it to email a maximum of a few times per day. Whichever the case, it is painfully slow dealing with them. The end result of this painfully slow email conversation is nothing, nada, zip, still getting null. The support group doesn’t have aqueduct documentation, no code samples, no code ownership and certainly no expertise.

 Long story shortened, this was my problem:

1
2
3
4
5
6
7
8
9
10
11
12
13
MessageHeader msgHeader = new MessageHeader();
 
// If the msgHeader.Action is not exactly 
// "TravelItineraryReadLLSRQ"
// your response will be null
msgHeader.Action = "TravelItineraryReadLLSRQ";
 
Service service = new Service();
service.Value = "TravelItineraryReadLLSRQ"; // THIS IS NOT USED!!!
service.type = "this object can be null";
msgHeader.Service = service; // THIS CAN BE NULL, IT'S NOT USED
 
. . .

Now I can finally start my research on Rail segments!!

There are not many “open” resources available for Sabre web services. I’ve written a lot of code to consume them…. contact me if you are stuck, maybe I can help.

Blogging C# Source in WordPress

1
2
3
4
5
6
7
8
9
10
11
12
13
using System;
namespace HelloWorld
{
    class Program
    {
        static void Main(string[] args)
        {
            Console.WriteLine("Hello World!");
            Console.WriteLine(Environment.NewLine + "Press Enter to exit.");
            Console.ReadLine();
        }
    }
}

New to WordPress so I fumbled around a bit figuring out a way to write about C#. I’m sure there are lots of ways to skin this cat but the first method I got working was with the WP-Syntax plugin. After installed, I copied my source from VisualStudio and stuffed it inside here :

<pre lang=”csharp” line=”1″> PASTE CODE HERE </pre>.