Debug SOAP and REST in IIS Express using Fiddler

I’ve been using Visual Studio 2013 Express to make a C# website that uses SOAP to request services from another company.  Everything was working fine, until one day I suddenly started getting HTTP 500 errors.  Naturally the service provider said it wasn’t them, so out came Fiddler.  Funnily enough the request worked straight away in Fiddler, so I needed to find out what the difference was between a Fiddler request and a request from my localhost.

I followed this guide here: http://docs.telerik.com/fiddler/Configure-Fiddler/Tasks/ConfigureDotNETApp

However:

GlobalProxySelection.Select = new WebProxy(“127.0.0.1”, 8888);

should now be:

WebRequest.DefaultWebProxy = new WebProxy(“127.0.0.1”, 8888);

Because of this issue, we can’t debug simply using ‘localhost’.  So I used this guide to use a custom host name as opposed to ‘localhost’ – using the following locations as reference for the applicationhost.config file:

Pre-Visual Studio 2015

%userprofile%\documents\iisexpress\config\applicationhost.config
%userprofile%\my documents\iisexpress\config\applicationhost.config

Visual Studio 2015+

$(solutionDir)\.vs\config\applicationhost.config

In Fiddler, because I’m using HTTPS, I had to go to Tools > Fiddler Options > HTTPS tab and check ‘Decrypt HTTPS traffic’

And finally I needed to add this line BEFORE my C# HttpWebRequest code for the request to work properly when Fiddler was running:

ServicePointManager.ServerCertificateValidationCallback +=(sender, cert, chain, sslPolicyErrors) => true;

I think that’s it…..

ASP.Net Upload Handler using SSH.Net and Plupload

Ok.  I won’t drag this out too much since there’s FAR too much to comment on.  But my aim was to create a web-based upload facility that is compatible with old browsers (such as IE8) and has the ability to upload over SFTP (ASP.Net doesn’t natively support this via the FtpWebRequest object).

So in summary I’ve used:

Plupload – This is the upload widget, which degrades nicely from HTML5 down to Flash, Silverlight and HTML4.  (note that Flash doesn’t support chunking of uploads)

SSH.Net – This is the free library I used to upload via SFTP.  It really is brilliant.

Here is an excerpt of the ASPX page containing Plupload.  Things to note:

  • It supports uploading of zip,rar,7z,iso file types only
  • It reports upload speed
  • It uses a default chunk size of 10mb (where chunking is supported)
  • Points to an upload handler called FileUpload.ashx
  • Passes 3 parameters to the upload handler – client, applicationID and uploadType
  • Has a button to reset the uploader
  • Informs user to use a newer browser for better performance (based on Plupload render type)
<!--ASPX Code....-->
            <div id="uploader">
                <p>Your browser doesn't have Flash, Silverlight or HTML5 support.</p>
            </div>
			<asp:LinkButton ID="btnReset" runat="server">Reset Uploader</asp:LinkButton>

            Upload Speed: <asp:Label ID="speedLbl" runat="server" Text="0 bytes/sec (0 kilobytes/sec)"></asp:Label>
      
			 
 <!--ASPX Code....-->
 
 <script type="text/javascript">
           

        // Initialize the widget when the DOM is ready
            $(function () {

            // Setup html5 version
            $("#uploader").pluploadQueue({
                // General settings
                runtimes: 'html5,flash,silverlight,html4',
                url : "FileUpload.ashx",
                chunk_size: '10mb',
                max_retries: 3,   
                rename : true,
                dragdrop: true, 
                // Specify what files to browse for               
                filters: {
                    mime_types : [
                      { title: "Compressed Files", extensions: "zip,rar,7z,iso" }
                    ]
                },
                init: attachCallbacks, 
                // Flash settings
                flash_swf_url : 'plugins/plupload/js/Moxie.swf',     
                // Silverlight settings
                silverlight_xap_url : 'plugins/plupload/js/Moxie.xap',
                 multipart_params: {
                     'client': '<%= clientLbl.Text %>',
                     'uamID': '<%= uamIDLbl.Text %>',
                     'uploadType': '<%= uploadTypeLbl.Text %>'
                        }
            });

            // attach callbacks for FileUploaded and Error
            function attachCallbacks(uploader) {
                uploader.bind('Error', function (up, error) {
                    alert("An error occurred.  Please try later or contact Support. " + error.message);

                });
                uploader.bind('UploadComplete', function (up, files) {
                    //files are uploaded, call script for each file...etc
                     if (up.total.failed > 0)
                    {
                        //files are uploaded, call script for each file...etc
                        alert("Upload Error");
                    }
                    else
                    {
                        //files are uploaded, call script for each file...etc
                        alert("Upload Completed Successfully");
                    }
                });
                uploader.bind('StateChanged', function (up) {
                    //files are uploaded, call script for each file...etc
                  
                    if (up.state == plupload.STARTED)
                    {
                        //started upload
                        alert("Started Upload.....Please Wait");
                    }
                    else
                    {
                        //stopped upload
                    }
                   
                });             

                uploader.bind('FileUploaded', function (up, file, response) {
           
                    //display error if file upload failed
                    var jsonResponse = jQuery.parseJSON(response.response);                  

                     if (jsonResponse.result !== null && jsonResponse.result != 'Success') {
                        file.status = plupload.FAILED;
                        alert(file.name + ': ' + jsonResponse.result);                     
                    }
                    else
                    {
                        alert(file.name + ' uploaded successfully!');
                    }

                });

                uploader.bind('UploadProgress', function (up, file) {     
                    var speedb = up.total.bytesPerSec;
                    var speedkb = Math.round((speedb / 1024) * 100) / 100;
                    $("#speedLbl").text(speedb + " bytes/sec (" + speedkb + " kilobytes/sec)");
                });

                uploader.bind('PostInit', function (up) {
                    var currentRuntime = up.runtime;

                    if (currentRuntime == "flash") {
                          alert("We recommend using a more recent browser such as Internet Explorer 10 or Google Chrome to upload larger files (over 2GB) and to improve upload performance.");
                    }
                   
                });
            }

        });

            //tweak to reset the interface for new file upload, the core API didn't provide this functionality
            $('#btnReset').click(function () {
                var uploader = $('#uploader').pluploadQueue();

                //clear files object
                uploader.files.length = 0;

                $('div.plupload_buttons').css('display', 'block');
                $('span.plupload_upload_status').html('');
                $('span.plupload_upload_status').css('display', 'none');
                $('a.plupload_start').addClass('plupload_disabled');
                //resetting the flash container css property
                $('.flash').css({
                    position: 'absolute', top: '292px',
                    background: 'none repeat scroll 0% 0% transparent',
                    width: '77px',
                    height: '22px',
                    left: '16px'
                });
                //clear the upload list
                $('#uploader_filelist li').each(function (idx, val) {
                    $(val).remove();
                });
            });
        </script>

And here is the upload handler, FileUpload.ashx.  I’ve stripped some logic out where I obtain things like ftpPassword etc (I obtain these in reality by running a CAML query on a SharePoint list).  When I obtain this encrypted password from SharePoint (based on the ‘client’ parameter passed in from Plupload), I decrypt it using the Rijndael decryption method here.  Also, AlkaneLogging is just a logging class I wrote which writes debug messages to a text file.  Comment these lines out if needs be:

using System;
using System.Web;
using System.Net;
using System.IO;
using System.Text;
using Microsoft.SharePoint;
using System.Configuration;
using System.Web.SessionState;
using System.Web.Script.Serialization;
using Renci.SshNet;
using System.Security.Cryptography;

namespace AlkaneSolutions.Layouts.AlkaneSolutions
{
    
	public class FileUpload : IHttpHandler, IRequiresSessionState
	{

		//important to implement IRequiresSessionState
		//IReadOnlySessionState (for read-only access) or IRequiresSessionState (for read-write access)

		public bool IsReusable
		{
			get
			{
				return false;
			}
		}

		int chunk = 0;
		int chunks = 0;
		string fileName = "";
		string fileExt = "";
		string client = "";
		string applicationID = "";
		string uploadType = "";
		
		//unique session names based on applicationID and UploadType
		string ftpUsernameUniqueSessionName = "";
		string ftpPasswordUniqueSessionName = "";
		string ftpUploadProtocolUniqueSessionName = "";
		string ftpUploadPortUniqueSessionName = "";
		string ftpServerUniqueSessionName = "";
		string ftpDownloadProtocolUniqueSessionName = "";
		string ftpDownloadPortUniqueSessionName = "";
	  
		string ftpUsername = "";
		string ftpPassword = "";
		string ftpSalt = "";
		string ftpUploadProtocol = "";
		int ftpUploadPort = 0;
		string ftpServer = "";
		string ftpDownloadProtocol = "";
		string ftpDownloadPort = "";
		
	   
		public void ProcessRequest(HttpContext context)
		{
			try
			{

				if (context.Request.Files.Count > 0)
				{

					for (int a = 0; a <= context.Request.Files.Count - 1; a++)
					{
						chunk = context.Request["chunk"] != null ? int.Parse(context.Request["chunk"]) : 0;
						chunks = context.Request["chunks"] != null ? int.Parse(context.Request["chunks"]) -1 : 0;
						fileName = context.Request["name"] ?? string.Empty;
						fileExt = Path.GetExtension(fileName).ToLower();
						client = context.Request["client"] ?? string.Empty;
						applicationID = context.Request["applicationID"] ?? string.Empty;
						uploadType = context.Request["uploadType"] ?? string.Empty;
					   
						//set up unique session names per application (useful for concurrent application uploads where applicationID is unique.  These sessions are used 
						//when we upload using chunks (so we dont keep having to read from a data source for every chunk!)
						
						ftpUsernameUniqueSessionName = applicationID + "_" + uploadType + "_ftpUsername";
						ftpPasswordUniqueSessionName = applicationID + "_" + uploadType + "_ftpPassword";
						ftpUploadProtocolUniqueSessionName = applicationID + "_" + uploadType + "_ftpUploadProtocol";
						ftpUploadPortUniqueSessionName = applicationID + "_" + uploadType + "_ftpUploadPort";
						ftpServerUniqueSessionName = applicationID + "_" + uploadType + "_ ftpServer";
						ftpDownloadPortUniqueSessionName = applicationID + "_" + uploadType + "_ftpDownloadPort";
						ftpDownloadProtocolUniqueSessionName = applicationID + "_" + uploadType + "_ftpDownloadProtocol";

					  
						if (fileExt != ".zip" && fileExt != ".7z" && fileExt != ".rar" && fileExt != ".iso")
						{
							AlkaneLogging.WriteToLog("File Upload Error: " + applicationID + " - illegal extension of " + fileExt);
							context.Response.ContentType = "application/json";
							context.Response.ContentEncoding = Encoding.UTF8;
							context.Response.StatusCode = 601;
							context.Response.Write("{\"jsonrpc\" : \"2.0\", \"error\" : {\"code\": \"601\", \"message\": " + JsonEncode("Invalid File Type") + "}, \"id\" : \"id\"}");
							context.ApplicationInstance.CompleteRequest();
						}
					   

						if (chunk == 0)
						{
							//if it's the first chunk get FTP credentials (I've removed lots of code here - I obtain them from a SharePoint query but you may use any other data source)
						   
							ftpUsername = "ftpUsername";
							ftpSalt = "ftpSalt";
							//I retrieve the encrypted password stored in SharePoint and decrypt it here.
							ftpPassword = Decrypt(ftpPassword, ftpUsername, ftpSalt, "InitialisationVector");
							ftpServer = "ftpServer";
							ftpUploadPort = 25;
							ftpUploadProtocol = "ftpUploadProtocol";
							ftpDownloadProtocol = "ftpDownloadProtocol";
							ftpDownloadPort = "ftpDownloadPort";

							//store in session, as we don't want to keep searching Sharepoint for each subsequent chunk!
							context.Session[ftpUsernameUniqueSessionName] = ftpUsername;
							context.Session[ftpPasswordUniqueSessionName] = ftpPassword;
							context.Session[ftpUploadProtocolUniqueSessionName] = ftpUploadProtocol;
							context.Session[ftpUploadPortUniqueSessionName] = ftpUploadPort;
							context.Session[ftpServerUniqueSessionName] = ftpServer;
							context.Session[ftpDownloadPortUniqueSessionName] = ftpDownloadPort;
							context.Session[ftpDownloadProtocolUniqueSessionName] = ftpDownloadProtocol;
							context.Session[ftpDownloadPortUniqueSessionName] = ftpDownloadPort;
							 
						}
						else
						{
							//keep session alive for each chunk by resetting it                    
							context.Session[ftpUsernameUniqueSessionName] = (string)(context.Session[ftpUsernameUniqueSessionName]);
							context.Session[ftpPasswordUniqueSessionName] = (string)(context.Session[ftpPasswordUniqueSessionName]);
							context.Session[ftpUploadProtocolUniqueSessionName] = (string)(context.Session[ftpUploadProtocolUniqueSessionName]);
							context.Session[ftpUploadPortUniqueSessionName] = (int)(context.Session[ftpUploadPortUniqueSessionName]);
							context.Session[ftpServerUniqueSessionName] = (string)(context.Session[ftpServerUniqueSessionName]);
							context.Session[ftpDownloadPortUniqueSessionName] = (string)(context.Session[ftpDownloadPortUniqueSessionName]);
							context.Session[ftpDownloadProtocolUniqueSessionName] = (string)(context.Session[ftpDownloadProtocolUniqueSessionName]);
					 
							//we'll need to set these vars in case it isn't the first chunk
							ftpUsername = (string)(context.Session[ftpUsernameUniqueSessionName]);
							ftpPassword = (string)(context.Session[ftpPasswordUniqueSessionName]);
							ftpUploadProtocol = (string)(context.Session[ftpUploadProtocolUniqueSessionName]);
							ftpUploadPort = (int)(context.Session[ftpUploadPortUniqueSessionName]);
							ftpServer = (string)(context.Session[ftpServerUniqueSessionName]);
							ftpDownloadPort = (string)(context.Session[ftpDownloadPortUniqueSessionName]);
							ftpDownloadProtocol = (string)(context.Session[ftpDownloadProtocolUniqueSessionName]);
						}

						AlkaneLogging.WriteToLog("Server: " + ftpServer + "\r\nUsername: " + ftpUsername + "\r\nPassword: " + ftpPassword + "\r\nUpload Protocol: " + ftpUploadProtocol + "\r\nUpload Port: " + ftpUploadPort + "\r\nDownload Protocol: " + ftpDownloadPort + "\r\nDownload Port: " + ftpDownloadPort);
	  
						//construct upload string
						string remoteDirectory = "SOURCE/" + applicationID + "/";
						string remoteFilePath = remoteDirectory + fileName;
						string SFTPFilePath = ftpUploadProtocol + "://" + ftpServer + ":" + ftpUploadPort + "/SOURCE/" + applicationID + "/" + fileName;

						AlkaneLogging.WriteToLog("Attempting to upload: " + SFTPFilePath + " with creds: " + ftpUsername + " " + ftpPassword);

						try
						{
							using (var ftp = new SftpClient(ftpServer, ftpUploadPort, ftpUsername, ftpPassword))
							{
								//connect to FTP stream
							   ftp.Connect();                           
								
								AlkaneLogging.WriteToLog("Connected successfully to: " + ftpServer);

								AlkaneLogging.WriteToLog("Attempting to create stream to: " + remoteFilePath);

								//if first chunk, create.  If subsequent chunk append
								using (var destStream = ftp.Open(remoteFilePath, chunk != 0 ? FileMode.Append : FileMode.CreateNew, FileAccess.Write))
								{
									//if remote folder doesnt exist, create it!
									if (!ftp.Exists(remoteDirectory))
									{
										AlkaneLogging.WriteToLog("Creating directory: " + remoteDirectory);
										ftp.CreateDirectory(remoteDirectory);
									}

									AlkaneLogging.WriteToLog("Writing chunk " + (chunk).ToString() + " of " + chunks.ToString() + " to stream");

									Stream streamReader = context.Request.Files[a].InputStream;
									byte[] bFile = new byte[streamReader.Length];

									streamReader.Read(bFile, 0, (int)streamReader.Length);
									streamReader.Close();
									streamReader.Dispose();

									int offset = 0;
									int buffer = (bFile.Length > 2048) ? 2048 : bFile.Length;
									while (offset < bFile.Length)
									{
										destStream.Write(bFile, offset, buffer);
										offset += buffer;
										buffer = (bFile.Length - offset < buffer) ? (bFile.Length - offset) : buffer;
									}
									destStream.Flush();
									destStream.Close();
									
							  
									AlkaneLogging.WriteToLog("File chunk for " + fileName + " uploaded successfully");

								}

								//disconnect from SFTP object
								ftp.Disconnect();


							}

							//if no chunks (maybe not supported by browser or not specified) or final chunk
							if ((chunks == 0) || (chunk == (chunks)))
							{

								ftpDownloadPort = (string)(context.Session[ftpDownloadPortUniqueSessionName]);
								ftpDownloadProtocol = (string)(context.Session[ftpDownloadProtocolUniqueSessionName]);
					  
								string SharepointFTPPath = ftpDownloadProtocol + "://" + ftpServer + ":" + ftpDownloadPort + "?u=" + (string)(context.Session[ftpUsernameUniqueSessionName]) + "&p=" + (string)(context.Session[ftpPasswordUniqueSessionName]) + "&path=" + "/SOURCE/" + applicationID + "/" + fileName;
							  
								//update Sharepoint/database with upload path (removed in this code excerpt)
							  
								//remove FTP credentials from session
								context.Session.Remove(ftpUsernameUniqueSessionName);
								context.Session.Remove(ftpPasswordUniqueSessionName);

								AlkaneLogging.WriteToLog("File Upload Success: " + applicationID);

                                context.Response.ContentType = "application/json";
                                context.Response.ContentEncoding = Encoding.UTF8;
                                context.Response.StatusCode = 200; 
                                context.Response.Write("{\"jsonrpc\" : \"2.0\", \"result\" : null, \"id\" : \"id\"}");
								context.ApplicationInstance.CompleteRequest();
								
							}


						}
						catch (Exception ex)
						{
							AlkaneLogging.WriteToLog("File Upload Error: " + applicationID + " " + ex.Message);
							context.Response.Write(ex.Message);
							context.Response.StatusCode = 500;
							context.ApplicationInstance.CompleteRequest();
						}
					}
				}
				else
				{
					AlkaneLogging.WriteToLog("File Upload Error: " + applicationID + " - no files to upload");
					context.Response.Write("No files to upload");
					context.Response.StatusCode = 500;
					context.ApplicationInstance.CompleteRequest();
				}
			}
			catch (WebException e)
			{
				String status = ((FtpWebResponse)e.Response).StatusDescription;
				AlkaneLogging.WriteToLog("File Upload Error: " + applicationID + " - " + status);
				context.Response.Clear();
				context.Response.Write(status);
                context.Response.StatusCode = 500;
                context.ApplicationInstance.CompleteRequest();
			}
		}

		 

		public static bool FTPFileExists(string ftpServer, int ftpPort, string ftpUsername, string ftpPassword, string ftpFile)
		{
			bool fileExists = false;
			
			try
			{
				using (var ftp = new SftpClient(ftpServer, ftpPort, ftpUsername, ftpPassword))
				{   
					ftp.Connect();

					if (ftp.Exists(ftpFile))
					{
						AlkaneLogging.WriteToLog(ftpFile + " exists");
						fileExists = true;
					}
					else
					{
						AlkaneLogging.WriteToLog(ftpFile + " does not exist");                  
						fileExists = false;
					}
					
					//disconnect from SFTP object
					ftp.Disconnect();
					
					return fileExists;
				}
			}
			catch
			{
				AlkaneLogging.WriteToLog(ftpFile + " does not exist");
				return fileExists;
			}
		   
		}

		
		protected string JsonEncode(object value)
		{
			var ser = new JavaScriptSerializer();
			return ser.Serialize(value);
		}

	}

}

Rijndael Encryption and Decryption in C# and Powershell

This is an example of how we can perform Rijndael encryption and decryption in C# and Powershell. It’s worth mentioning that:

– salt must be 8 bytes minimum
– initVector must be 16 bytes minimum

The examples below use Cipher Block Chaining (CBC), zero padding, 10000 key iterations and 32 byte (256/8) block cipher.

Powershell:

[Reflection.Assembly]::LoadWithPartialName("System.Security") | Out-Null

#salt must be 8 bytes minimum
#initVector must be 16 bytes minimum

function Encrypt-String($stringToEncrypt, $passPhrase, $salt, $initVector)
{
	# Create a COM Object for RijndaelManaged Cryptography
	$rm = new-Object System.Security.Cryptography.RijndaelManaged
	$rm.Mode = [System.Security.Cryptography.CipherMode]::CBC;
	$rm.Padding = [System.Security.Cryptography.PaddingMode]::zeros;
	
	$keyIterations = 10000;
	
	# Convert the Salt to UTF Bytes
	$saltBytes = [Text.Encoding]::UTF8.GetBytes($salt)
	
	# Get string to encrypt in byte format
	$plainTextBytes = [Text.Encoding]::UTF8.GetBytes($stringToEncrypt)
	
	# Get initialisation vector byte format
	$initVectorBytes = [Text.Encoding]::UTF8.GetBytes($initVector)
	
	# Hash the user passphrase with the salt provided to create a key
	$keyBytes = (new-Object Security.Cryptography.Rfc2898DeriveBytes $passPhrase, $saltBytes, $keyIterations).GetBytes(256 / 8);
			
	# Starts the Encryption using the Key and Initialisation Vector   
	$c = $rm.CreateEncryptor($keyBytes,$initVectorBytes);
	
	# Creates a MemoryStream to do the encryption in
	$ms = new-Object IO.MemoryStream
	
	# Creates the new Cryptology Stream --> Outputs to Memory Stream
	$cs = new-Object Security.Cryptography.CryptoStream $ms,$c,"Write"	
	
	#Writes the string in the Cryptology Stream
    	$cs.Write($plainTextBytes, 0, $plainTextBytes.Length);
    	$cs.FlushFinalBlock();
    
	#Takes the MemoryStream and puts it to an array
    	[byte[]]$cipherTextBytes = $ms.ToArray();  
	
	# Stops the Cryptology Stream
	$cs.Close()
	
	# Stops writing to Memory
	$ms.Close()
	
	# Clears the IV and HASH from memory to prevent memory read attacks
	$rm.Clear()
	
	# Takes the MemoryStream and puts it to an array
	[byte[]]$rmesult = $ms.ToArray()
	
	# Converts the array from Base 64 to a string and returns
	return [Convert]::ToBase64String($rmesult)
}

function Decrypt-String($stringToDecrypt, $passPhrase, $salt, $initVector)
{

	# Create a COM Object for RijndaelManaged Cryptography
	$rm = new-Object System.Security.Cryptography.RijndaelManaged
	$rm.Mode = [System.Security.Cryptography.CipherMode]::CBC;
	$rm.Padding = [System.Security.Cryptography.PaddingMode]::zeros;
	
	$keyIterations = 10000;
	
	$cipherTextBytes = [Convert]::FromBase64String($stringToDecrypt)
		
	# Convert the Salt to UTF Bytes
	$saltBytes = [Text.Encoding]::UTF8.GetBytes($salt)
	
	# Get initialisation vector byte format
	$initVectorBytes = [Text.Encoding]::UTF8.GetBytes($initVector)
	
	# Hash the user passphrase with the salt provided
	$keyBytes = (new-Object Security.Cryptography.Rfc2898DeriveBytes $passPhrase, $saltBytes, $keyIterations).GetBytes(256 / 8);
		
	# Starts the Decryption using the Key and Initialisation Vector
	$d = $rm.CreateDecryptor($keyBytes,$initVectorBytes)
		
	# Create a New memory stream with the encrypted value.
	$ms = new-Object IO.MemoryStream @(,$cipherTextBytes)

	# Read the new memory stream and read it in the cryptology stream
	$cs = new-Object Security.Cryptography.CryptoStream $ms,$d,"Read"

	$decryptedByteCount = 0;
	
	$decryptedByteCount = $cs.Read($cipherTextBytes, 0, $cipherTextBytes.Length);
    	$cs.Close();
	
	# Stops the cryptology stream
	$cs.Close()

	# Stops the memory stream
	$ms.Close()

	# Clears the RijndaelManaged Cryptology IV and Key
	$rm.Clear()	
	
	return [Text.Encoding]::UTF8.GetString($cipherTextBytes, 0, $decryptedByteCount).TrimEnd([char]0x0000); 


}



$exampleString = "alkane"

write-host "String to encrypt is: $exampleString"

$stringToDecrypt = Encrypt-String  $exampleString "bxwD5j4LK4NEvlakuPp1g==" "JU=JVGLSFSgN4=!-LW" "H2+_=S*QFA=P!Gu_"

write-host "Encrypted is: $stringToDecrypt"

$decrypted =  Decrypt-String $stringToDecrypt "bxwD5j4LK4NEvlakuPp1g==" "JU=JVGLSFSgN4=!-LW" "H2+_=S*QFA=P!Gu_"

write-host "Decrypted is: $decrypted"

C#

using System.Security.Cryptography;

 public static string Encrypt(string plainText, string passPhrase, string salt, string initVector)
    {
        //Create instance of RijndaelManaged
        var symmetricKey = new RijndaelManaged() { Mode = CipherMode.CBC, Padding = PaddingMode.Zeros };

        //iterations to derive key
        int keyIterations = 10000;

        //Convert the cipher text to UTF Bytes
        byte[] plainTextBytes = Encoding.UTF8.GetBytes(plainText);

        //Hash the user passphrase with the salt provided to create a key
        byte[] keyBytes = new Rfc2898DeriveBytes(passPhrase, Encoding.ASCII.GetBytes(salt), keyIterations).GetBytes(256 / 8);
      
        //Starts the Encryption using the Key and Inititalisation Vector 
        var encryptor = symmetricKey.CreateEncryptor(keyBytes, Encoding.ASCII.GetBytes(initVector));

        byte[] cipherTextBytes;

        //Creates a MemoryStream to do the encryption in
        using (var memoryStream = new MemoryStream())
        {
            //Creates the new Cryptology Stream --> Outputs to Memory Stream
            using (var cryptoStream = new CryptoStream(memoryStream, encryptor, CryptoStreamMode.Write))
            {
                //Writes the string in the Cryptology Stream
                cryptoStream.Write(plainTextBytes, 0, plainTextBytes.Length);
                cryptoStream.FlushFinalBlock();
                //Takes the MemoryStream and puts it to an array
                cipherTextBytes = memoryStream.ToArray();
                cryptoStream.Close();
            }
            memoryStream.Close();
        }
        //Converts the array from Base 64 to a string and returns
        return Convert.ToBase64String(cipherTextBytes);
    }

    public static string Decrypt(string encryptedText, string passPhrase, string salt, string initVector)
    {

    
        //Create instance of RijndaelManaged
        var symmetricKey = new RijndaelManaged() { Mode = CipherMode.CBC, Padding = PaddingMode.Zeros };

        //iterations to derive key
        int keyIterations = 10000;

        //Convert the plain text to UTF Bytes
        byte[] cipherTextBytes = Convert.FromBase64String(encryptedText);

        //Hash the user passphrase with the salt provided to create a key
        byte[] keyBytes = new Rfc2898DeriveBytes(passPhrase, Encoding.ASCII.GetBytes(salt), keyIterations).GetBytes(256 / 8);
    
        //Starts the Deryption using the Key and Inititalisation Vector 
        var decryptor = symmetricKey.CreateDecryptor(keyBytes, Encoding.ASCII.GetBytes(initVector));

        int decryptedByteCount = 0;

        //Creates a MemoryStream to do the decryption in, with the encrypted value
        using (var memoryStream = new MemoryStream(cipherTextBytes))
        {
            //Read the new memory stream and read it in the cryptology stream
            using (var cryptoStream = new CryptoStream(memoryStream, decryptor, CryptoStreamMode.Read))
            {
                decryptedByteCount = cryptoStream.Read(cipherTextBytes, 0, cipherTextBytes.Length);
                cryptoStream.Close();
            }
            memoryStream.Close();
        }

        return Encoding.UTF8.GetString(cipherTextBytes, 0, decryptedByteCount).TrimEnd('\0');

    }

and call using:

 string encryptedString = Encrypt("alkane", "bxwD5j4LK4NEvlakuPp1g==", "JU=JVGLSFSgN4=!-LW", "H2+_=S*QFA=P!Gu_");
 string decryptedString = Decrypt(encryptedString , "bxwD5j4LK4NEvlakuPp1g==", "JU=JVGLSFSgN4=!-LW", "H2+_=S*QFA=P!Gu_");

Getting Public Key Token of Assembly with Powershell

When I need to add definitions to the web.config of my ASP.Net projects I often use this Powershell line to get the PublicKeyToken, Culture and Version etc.

([system.reflection.assembly]::loadfile("C:\AlkaneSolutions.dll")).FullName

Running this will yield a similar output to the following:

AlkaneSolutions, Version=1.0.0.0, Culture=neutral, PublicKeyToken=aa13051522563c02

Integrating Lync Status into an ASP.Net Gridview

This post describes integrating Lync status into an ASP.Net Gridview.  I had a bit of a nightmare trying to achieve this so that it worked on IE8 all the way up to IE11 .  First I had it working on IE8 but for IE9+ the status always showed as offline.  I finally made it work using the example below (using Lync 2010):

First create the Gridview – in this example we’re binding ‘analysts’ :

<asp:GridView ID="analystGV" runat="server" AutoGenerateColumns="False">
<Columns>
	<asp:TemplateField AccessibleHeaderText="ApplicationTrackerAssignedTo" HeaderText="Analyst">
		<ItemTemplate>
            <asp:Label ID="lyncStatus" data-id='<%# Eval("AnalystEmail") %>' runat="server" Text='<%# Eval("AnalystName") %>' onmouseover='<%# Eval("AnalystEmail", "javascript:ShowOOUI(this.id,&#39;{0}&#39;);") %>' onmouseout='HideOOUI()'></asp:Label>
        </ItemTemplate>
        <HeaderTemplate>
			Analyst
        </HeaderTemplate>  
    </asp:TemplateField>
</Columns>
</asp:GridView>

You’ll notice that the ID of the label is called lyncStatus, which will obviously generate a unique ID (for example ‘analystGV_ctl01_lyncStatus’, ‘analystGV_ctl02_lyncStatus’ etc) for each label when the Gridview renders.  We also use a data-id attribute to store the email address of the analyst.  I’ve seen examples in the internet where people have used the SIP URI as the ID on fields, but this won’t work in a Gridview if we’re expecting multiple instances of the same SIP (the same user, for example) and as the ID would suggest, it would not be unique!

The Text of the label displays the analyst’s name.

Now paste the following into the <head> section:

<script src="http://ajax.googleapis.com/ajax/libs/jquery/1.11.0/jquery.min.js"></script>

<style type="text/css">
      
       .available {
            background: url('images/lyncStatusIcons/lync_available.png') no-repeat left center;
            padding-left: 20px;
        }

        .offline {
             background: url('images/lyncStatusIcons/lync_offline.png') no-repeat left center;
            padding-left: 20px;
        }
        
        .away {
             background: url('images/lyncStatusIcons/lync_away.png') no-repeat left center;
            padding-left: 20px;
        }
                
        .busy, .inacall {
             background: url('images/lyncStatusIcons/lync_busy.png') no-repeat left center;
            padding-left: 20px;
        }

        .donotdisturb {
             background: url('images/lyncStatusIcons/lync_dnd.png') no-repeat left center;
            padding-left: 20px;
        }      

</style>

Above we include the JQuery library and specify some styles for the status of each user.

Here are the icons I use:

 

Finally, just before the </body> tag, paste this:

 <script type="text/javascript">
       
        var nameCtrl;

        function onStatusChange(name, status, id) {
            // This function is fired when the contacts presence status changes.
            // In a real world solution, you would want to update an image to reflect the users presence

            var sipClassName = getLyncPresenceString(status);

            document.getElementById(id).className = "";
            document.getElementById(id).className = sipClassName;

            //since it doesnt get the status correctly for duplicate users (GetStaus return code is incorrect and this function isn't called)
            //we get the first instance and search for other duplicate users and set the class

            $('span[data-id="' + name + '"]').each(function () {
                analystID = $(this).attr('id');

                if (analystID != id) { //if it's not the current ID we're processing, set the class
                    document.getElementById(analystID).className = sipClassName;
                }
            });

        }

        function ShowOOUI(id, sipUri) {
            if (nameCtrl && nameCtrl.PresenceEnabled) {
                var rect = document.getElementById(id).getBoundingClientRect();
                x = rect.left;
                y = rect.top;
                nameCtrl.ShowOOUI(sipUri, 0, x, y);
            }
        }
        function HideOOUI() {
            if (nameCtrl && nameCtrl.PresenceEnabled) {
                nameCtrl.HideOOUI();
            }
        }

        function getLyncPresenceString(status) {

            switch (status) {
                case 0:
                    return 'available';
                    break;
                case 1:
                case 8:
                    return 'offline';
                    break;
                case 2:
                case 4:
                case 16:
                    return 'away';
                    break;
                case 3:
                case 5:
                    return 'inacall';
                    break;
                case 6:
                case 7:
                case 10:
                    return 'busy';
                    break;
                case 9:
                case 15:
                    return 'donotdisturb';
                    break;
                default:
                    return '';
            }
        }


        //try and create NameCtrl object
        try {
            nameCtrl = new ActiveXObject('Name.NameCtrl.1');
            nameCtrl.OnStatusChange = onStatusChange;
        }
        catch (err) {
            //cannot create object, so don't show status
            //in later versions of Lync we could try and create the object without using ActiveX, but since we're
            //using 2010 we wont.
        }

        $(document).ready(function () {
           
             //loop through every span element with an ID ending with 'lyncStatus' and get the status for that user
            $('span[id$="lyncStatus"]').each(function () {
                sipUri = $(this).attr('data-id');
                analystID = $(this).attr('id');
                                
                if (nameCtrl && nameCtrl.PresenceEnabled) {
                    //GetStatus doesn't work when calling for a SIP that GetStatus has already been called for!
                    nameCtrl.GetStatus(sipUri, analystID);
                }

            });

          
          });

</script>

When the document has loaded, this piece of JQuery iterates through all elements with an ID ending with ‘lyncStatus’.  it retrieves the id and the data-id attributes (the id so it knows what element to set the Lync status on, and the data-id which stores the SIP URI (email address in my case)).  If an instance of the nameCtrl object was successfully created, and presence is enabled, it attempts to get the status for that SIP.

**Note**

I experienced intermittent issues where duplicate users would not always be rendered correctly.  That is, when GetStatus was called for a duplicate user (let’s say the same user joe.bloggs@alkanesolutions.co.uk appears twice) it would not return the correct value and OnStatusChange would never be called.  To circumvent this issue, when the first instance of each user is ‘rendered’, I then use JQuery to find all other users with the same SIP URI and to give them the same status css class.  Not ideal, but it works!

**Another Note**

If you’re testing this on an x64 platform, you must launch the x86 version of Internet Explorer.  Otherwise it appears we cannot instantiate the Name.NameCtrl object, and we will get the ‘Automation server can’t create object’ error in our ‘catch’ block.

Also if you have Lync 2013 (and not 2010 like me) you may be able to get the integration working in Chrome/Firefox too by following instructions on how to instantiate the object here.

Create a custom debug log file in an ASP.Net Class

This is a quick example of how to create a custom debug log file in an ASP.Net class which you can use in your website.  If the file doesn’t exist, it creates it.  It names the file today’s date.  And records a date/time at the start of each log entry

.aspx.cs page

...
CustomDebug.WriteToLog("Blah Blah Blah...");
....

 

.cs class

using System;
using System.Web;
using System.IO;
using System.Globalization;


public class CustomDebug
{
	public CustomDebug()
	{
		//
		// TODO: Add constructor logic here
		//
	}

        public static void WriteToLog(string errorMessage)
        {
            try
            {
                string path = "E:\\Logs\\CustomDebug\\" + DateTime.Today.ToString("dd-MM-yy") + ".txt";
                if (!File.Exists(path))
                {
                    File.Create(path).Close();
                }
                using (StreamWriter w = File.AppendText(path))
                {             
                    w.WriteLine(DateTime.Now.ToString("dd-MM-yyyy HH:mm:ss") + ": Debug Message: " + errorMessage);
                    w.Flush();
                    w.Close();
                }
            }
            catch (Exception ex)
            { //handle exception
            }
        }
}

 

Getting and Setting Session State in HttpHandlers (ASHX files)

If you just want to read your Session State from an ASHX or HttpHandler, you need to implement IReadOnlySessionState.  If you want to write to your Session State, you must implement IRequiresSessionState.  Note that you will also need use the System.Web.SessionState namespace.  This post describes getting and setting session state in HttpHandlers (ASHX files).

<% @ webhandler language="C#" class="UploadFile" %>

using System;
using System.Web;
using System.Web.SessionState;

public class UploadFile : IHttpHandler, IRequiresSessionState
{
   public bool IsReusable { get { return true; } }
   
   public void ProcessRequest(HttpContext context)
   {
       string ftpUsername = "ftpUser";
       context.Session["ftpUsername"] = ftpUsername;
   }
}

 

Strip out style attributes in HTML

This post describes the process I use to strip out style attributes in HTML code using a regular expression.

My website is presenting data from a field in SharePoint.  This field uses HTML and CSS style attributes to construct the note.  A user would enter this data via a Sharepoint website, and my .Net website will present it elsewhere. The trouble is, when my site presents this data the message can look like a right mess.  Different fonts, different sizes and different colours (you’ve met those idiots before who like to use Comic Sans font in a professional environment, right?).  So before I present the data in a Literal control I decided to write a regular expression to strip out any style/class attributes etc.  And here is the .Net function (which I have in a class):

  //function to strip CSS styles etc from sharepoint notes
    public static string stripStyles(string message)
    {

        //replace non-ascii with empty string
        message = Regex.Replace(message, @"[^\u0000-\u007F]", string.Empty);

        //replace 3 or more BR with one BR
        message = Regex.Replace(message, "(?:\\s*<br[/\\s]*>\\s*){3,}", "");

        //remove any style attributes   
        message = Regex.Replace(message, "style=(\"|')[^(\"|')]*(\"|')", "");

        //remove any classe attributes
        message = Regex.Replace(message, "class=(\"|')[^(\"|')]*(\"|')", "");  

        //remove empty p tags
        message = Regex.Replace(message, "(<p>\\s*</p>|<p>\\s*​\\?</p>)", "");
        
        //remove font tags
        message = Regex.Replace(message, "</?(font)[^>]*>", "");

        return message;

    }

It won’t produce perfect results, because there are also uses of the <font> tag scattered about in these messages.  But I’m going to leave those alone for now since I suspect <font> tags may be used to highlight (bold/colour) certain words (auto-generated from the WYSIWYG editor).

Re-bind the GridView on the Parent Page after FancyBox close

I have a GridView with hyperlinks in some of the columns, which open up a new page in a FancyBox.  FancyBox is a pretty cool lightbox alternative.  Anyway, in the FancyBox page content you can make changes which will affect values in the parent page’s GridView.  So when the FancyBox is closed, I needed to refresh the parent GridView.  Here is how you can re-bind the GridView on the Parent Page after FancyBox close:

First I create a button, gave it an OnClick event, and used CSS to make it hidden:

<asp:Button ID="fancyBt" OnClick="fancyBt_Click" style="visibility: hidden; display: none;" runat="server" Text="Hidden - used to update GV on FancyBox close" />

FancyBox has a handy afterClose function which, unsurprisingly, gets calledafter the FancyBox is closed.  What it does in here is call the click event of my hidden button:

$('.fancybox').fancybox({
    'transitionIn': 'elastic',
    'transitionOut': 'elastic',
    'speedIn': 600,
    'speedOut': 200,
    'overlayShow': false,
    'width': 1000,           // set the width
    'height': 400,           // set the height
    'type': 'iframe',       // tell the script to create an iframe
     'scrolling': 'no',
    afterClose: function () {
        //refresh GV
        document.getElementById('<%=fancyBt.ClientID%>').click();
    }
});

and then of course, the code behind for the button click event should rebind the gridview!

protected void fancyBt_Click(object sender, EventArgs e)
{
    //rebind GridView
}