Friday, May 24, 2013

Publishing encrypted connection strings in web.config


PROBLEM: You publish your website to a server (e.g. Stage or Production) and then have to manually run a command to encrypt the connectionStrings within the web.config (would be nice for the publish process to just take care of everything) - this guy on stackoverflow had the same issue: http://stackoverflow.com/questions/14838156/encrypting-webconfig/16728000

SOLUTION: Take the encrypted portion of the web.config (remember you'll need to be remoted on to your server to do the encryption) and add to your Web.[CONFIGURATION].config transformation file e.g. Web.Stage.config or Web.Release.config... since the encryption is based on machine keys you'll need to have a different transformation file for each server that you are deploying to.  This is all quite simple, I just never thought of doing it before - the hardest bit was finding the syntax for the web.config transformation file - the syntax I've got below works but the compiler will flag a warning about invalid syntax i.e.


Warning 15 The element 'connectionStrings' has invalid child element 'EncryptedData' in namespace 'http://www.w3.org/2001/04/xmlenc#'. List of possible elements expected: 'add, remove, clear'. C:\DevTFS\YourProject\Web.Stage.config 14 6 YourProject

 (I'm open to suggestions on how to make it compliant and still work).


Step 1.
Encrypt connectionStrings in the web.config as per: http://msdn.microsoft.com/library/dtkwfdky.aspx or http://stackoverflow.com/questions/8230864/how-can-i-safely-store-and-access-connection-string-details

I keep a batch file in the root of my website for this:


@ECHO OFF
echo "This will Encrypt or Decrypt the Connections section of web.config - it should be run after deploying to any publicly accessible version of the site e.g. production."

CHOICE /C:ed /M "Encrypt or Decrypt"

IF %errorlevel%==1 goto enc
IF %errorlevel%==2 goto dec

:enc
echo "Encrypting section connectionStrings"
c:\WINDOWS\Microsoft.net\Framework\v4.0.30319\aspnet_regiis -pef "connectionStrings" %CD%
goto EOF

:dec
echo "Decrypting section connectionStrings"
c:\WINDOWS\Microsoft.net\Framework\v4.0.30319\aspnet_regiis -pdf "connectionStrings" %CD%
GOTO EOF

:EOF
EXIT



Step 2.

Add the following to your Web.[Config].config file...


  <connectionStrings configProtectionProvider="RsaProtectedConfigurationProvider"
  </connectionStrings>

And then copy and paste the <EncryptedData> elements from your encrypted web.config file on your server between these tags e.g.


  <connectionStrings configProtectionProvider="RsaProtectedConfigurationProvider" xdt:Transform="Replace">
    <EncryptedData Type="http://www.w3.org/2001/04/xmlenc#Element"
      xmlns="http://www.w3.org/2001/04/xmlenc#">
      <EncryptionMethod Algorithm="http://www.w3.org/2001/04/xmlenc#tripledes-cbc" />
      <KeyInfo xmlns="http://www.w3.org/2000/09/xmldsig#">
        <EncryptedKey xmlns="http://www.w3.org/2001/04/xmlenc#">
          <EncryptionMethod Algorithm="http://www.w3.org/2001/04/xmlenc#rsa-1_5" />
          <KeyInfo xmlns="http://www.w3.org/2000/09/xmldsig#">
            <KeyName>Rsa Key</KeyName>
          </KeyInfo>
          <CipherData>
           <CipherValue>t8p7aOZTjMon8B1qC4L4gmKasdfsdafasdfJHckY0fl9hfaasdffQWrpdX1jqF6vD3/X4Ejg+UeiCWujkx+dfvDOif3sodfsdfsd6kHAtah2o59UmzsfdasdfdfdKzUliSgMe01fRbjA/bxA6Bbq+sjzE6FAAI=</CipherValue>
          </CipherData>
        </EncryptedKey>
      </KeyInfo>
      <CipherData>
        <CipherValue>Vy1TZWY8ojpf343XQCQwK/r4lmp+vbJPS5sdfdfbv0YMTGEdGuCwdLND5ezMe9iLkuI5/fmvU1TSDzPgvKcAwNc1rXU5jiU0234234JtviOMe6vjU8FSkHilwLITGS9/XUDiacqccfuXsBcdBtcwAfBxIAwwCQxOQIFi6hN/cG2emFj1oSIU468O8ezOG+UMSd4HzaDS2jzZyrfsdfsdfyi0bg8OV5QVOlSUjjuh54Bt4t2pd0O2vsUbwsdfsdfVxB0KgIlL6Kqe53z2Ns6GHlRwJuMFRHQnQT234234SSVLLGkWdI1IGyl12JdlTrd5JItDHGgPNat+fe5FR5GNasdfsdfivft4YZV3iXgbPtZyiHm6aI7ccDuCTHJ+V78AwZAVlIGRKzVbqsic+Qg6T7U</CipherValue>
      </CipherData>
    </EncryptedData>
  </connectionStrings>


Credit goes to this guy for actually adding a useful comment to forums.asp.net (unfortunately they are few and far between) - http://forums.asp.net/post/5390287.aspx

Thursday, January 24, 2013

String Truncate

PROBLEM: The .NET framework doesn't have a truncate method for strings.  Substring will fail if you're not careful (i.e. if you don't check that the string has at least as many characters at the truncation length)

SOLUTION: Add a Truncate extension method for the string class that does the boring checks (length, null - some other examples don't include the null check, etc) for you each time.


        /// <summary>
        /// Ensure that a string is no longer than a specified maximum number of characters.
        /// This string extension method has been written because there is no string truncate method
        /// in the .NET framework and Substring will throw an exception if the length you enter is
        /// longer than the length of the string.
        /// </summary>
        /// <param name="originalString"></param>
        /// <param name="maximumLength"></param>
        /// <returns></returns>
        public static string Truncate(this String originalString, int maximumLength)
        {
            return (originalString == null || originalString.Length <= maximumLength) ? originalString : originalString.Substring(0, maximumLength);
        }

Example use:

          string newString = oldString.Truncate(10);

Credit goes to this guy and one of the people in his comments: http://jamesfricker.blogspot.com.au/2007/08/truncating-string-in-c-easy-huh.html

Friday, January 11, 2013

OnClientClick breaks validation


PROBLEM: Your validators (RequiredFieldValidator, RangeValidator, RegularExpressionValidator etc) are not being called/fired when you have some client side javascript in OnClientClick that asks a yes/no question e.g. Are you sure you want to delete this?


<asp:Button ID="btnConfirmDelete"
                    runat="server"
                    Text="Delete this Session"
                    ValidationGroup="vgDelete"
                    onclick="btnConfirmDelete_Click"
                    OnClientClick='return confirm("Are you sure you want to delete this session?")' />


SOLUTION:
When the page is rendered, the javascript for the onclick will look something like:


onclick="return confirm('Are you sure?');WebForm_DoPostBackWithOptions(...)"

As you can see, the validation doesn't even have a chance to fire (which happens when WebForm_DoPostBackWithOptions is called).

(this guy figured that out: http://vaultofthoughts.net/OnClientClickBreaksValidation.aspx)

Changing it to the below fixes that:

'if (!confirm("Are you sure you want to delete this session?")) return false;'

BUT then you realise that the validators are being called AFTER the prompt (ideally you don't want to prompt the user that they're sure until the last minute after everything is completed). So the this version takes care of that:

'if(Page_ClientValidate()) return confirm("Are you sure you want to delete this session?"); return false;'

Some more info here: http://alvinzc.blogspot.com.au/2006/10/aspnet-requiredfieldvalidator.html

BUT if you're using validation groups, remember to specify it when you call Page_ClientValidate to prevent fields being validated that you don't want validated. Therefore the final version in our case is:

OnClientClick='if(Page_ClientValidate("vgDelete")) return confirm("Are you sure you want to delete this session?"); return false;'

http://techbrij.com/client-side-validation-using-asp-net-validator-controls-from-javascript
http://programmer.webhostingdevelopment.com/index.php/2010/03/specifying-validationgroup-in-page_clientvalidate-function/


Monday, October 1, 2012

Getting rid of "Only secure content is displayed" messages

PROBLEM: You have a secure page(s) on your site (e.g. online payment page) and you are getting "Only secure content is displayed" type warnings in your browser, but you're already using relative URLs for all your assets on your page.

SOLUTION: Have a look at any external assets e.g. Twitter javascript libraries etc. and see how you are linking to them.  You probably have hard coded the protocol - I'd suggest you use protocol-relative hyperlinks (this doesn’t apply to simply linking to another page e.g http://www.twitter.com, only for actual content that is being used on the page e.g. http://twitter.com/javascripts/blogger.js). So instead of having:


            <script type="text/javascript" src="http://twitter.com/javascripts/blogger.js"></script>
            <script type="text/javascript" src="http://twitter.com/statuses/user_timeline/UserXXX.json?callback=twitterCallback2&count=2"></script>

Use this instead:

            <script type="text/javascript" src="//twitter.com/javascripts/blogger.js"></script>
            <script type="text/javascript" src="//twitter.com/statuses/user_timeline/ UserXXX .json?callback=twitterCallback2&count=2"></script>

And this will help you avoid those ugly ‘mixed content’ security warnings which you normally brush under the carpet when browsing the web (obviously make sure that the external sites offer a HTTPS version of their resource).

Some more info is here:

http://weblogs.asp.net/jgalloway/archive/2009/10/15/did-you-know-about-protocol-relative-hyperlinks.aspx

An alternative approach is to use javascript like Google does

   ...
        ga.src = ('https:' == document.location.protocol ? 'https://ssl' : 'http://www') + '.google-analytics.com/ga.js';
   ....

Friday, September 14, 2012

Multiple HTTP Redirects in IIS 7

PROBLEM: On one of our websites we recently moved from having a separate mobile site vs full site to just having a full site with "responsive design".  We initially just put in place a HTTP Redirect (301) in place via IIS 7 for the mobile site to redirect all mobile pages to the home page of the full site (hoping that people would update their bookmarks after getting redirected) - feedback said people didn't want to update their bookmarks so we needed a way to put in place multiple redirects.

SOLUTION: I couldn't see a way to put multiple HTTP Redirect rules in place easily via IIS 7 for a single site so I decided to use "URL Rewrite 2.0" for IIS 7, a module/extension for IIS provided by Microsoft that has to be downloaded and installed separately (watch out for the first gotcha!)
  1. To install download it from  http://www.iis.net/downloads/microsoft/url-rewrite which will install it via the Web Platform Installer... BUT... be aware, that despite a lack of warning it will:
    1. Stop all the websites on your server.
    2. Require a server reboot before you can use it
  2. After you've scrambled to reboot your server after seeing all your sites down then it's simply a matter of opening the "URL Rewrite" module you'll now see in IIS for your site and creating a number of redirect rules - here is some info: http://www.iis.net/learn/extensions/url-rewrite-module/using-the-url-rewrite-module but this is what I did
    • Requested URL = Matches Pattern
    • Using = Regular Expressions (man, these always suck if you haven't learnt them! A cheat sheet might help you: http://regexlib.com/CheatSheet.aspx ... I find trying to search for regular expressions in regexlib is pointless if it's a general concept you're looking for e.g. "find me all matches with a word but not having this word" vs "email validator")
    • Pattern: e.g. (?!.*timetable.*)^clubs/(.*)/(.*)$
      • Matches any URL that starts with clubs/ and doesn't contain the word timetable
    • Another example: ^clubs/(.*)/(.*)/timetable.*$
      • Matches any URL that starts with clubs/ and then has timetable at the end
    • Ignore case = ticked
    • Action Type: Redirect
    • Redirect URL: e.g. http://yoursite.com/clubs/{R:1}/{R:2}/ (see how you can grab anything from the regular expression that was in brackets (known as a back reference apparently) and use it in the redirect URL.
    • Another example: http://yoursite.com/clubs/{R:1}/{R:2}/timetable/
    • Append Query String = false (not required since we were using friendly urls without query strings anyway).
    • Redirect Type: Permanent (301).
  3. To ensure all other pages are redirected to the root of your homepage simply update your HTTP Redirect settings in IIS
    1. Redirect requests to this destination: Ticked
    2. http://yoursite.com/
    3. Redirect all requests to exact destination (instead of relative to destination).
    4. Status code: Permanent (301).
I'm sure there are better ways out there and would love to hear them.  Otherwise hopefully this helps someone else - at the very least hopefully someone will read the warning about the installation of URL Rewrite causing your sites to stop and the server requiring a reboot!

Wednesday, June 6, 2012

Winforms ListControls (ListBox or ComboBox) binding performance gotcha

PROBLEM: Excessive rebinding can occur with ListControls in Winforms.

SOLUTION: Specifically when the DataSource is changed, or when DisplayMember or ValueMember is changed after DataSource has been set, the binding infrastructure forces the control to rebind. Logically, this makes sense. But it poses a problem if you handle certain control events, in particular SelectedIndexChanged and SelectedValueChanged. In the worst case, the following simple sequence will raise SelectedIndexChanged three times in a row:
listbox.DataSource = dataTable
listbox.ValueMember = "id"
listbox.DisplayMember = "name"

Not a big deal if nothing data intensive is happening in SelectedIndexChanged but in my case it often calls the code to call a webservice. So simply changing the order of these to be as below results in just one SelectedIndexChanged call instead of 3, and hence one webservice call instead of 3.

listbox.DisplayMember = "name"
listbox.ValueMember = "id"
listbox.DataSource = dataTable

This very smart fellow discusses it in full on codeproject: http://www.codeproject.com/KB/database/scomlistcontrolbinding.aspx

Wednesday, April 11, 2012

Checking Server Disk Space

PROBLEM: You have a large network of computers, you want to know when they are getting low on space since that can cause many issues such as websites running slowly or not at all, logs might not get written, defragging can't occur, windows updates can't be applied etc. So you want to monitor them constantly but don't want to waste time doing it.
SOLUTION: I'm sure there are products out there (free and paid for) to do this but my solution was to go the DIY path and use PowerShell and two Windows Scheduled Tasks (one daily that will email any warnings and one weekly that will email regardless). I have zero PowerShell experience but have borrowed liberally from a few fellows (attributions in the script). I'll give you the code and then raise a few points about it:


##############  Script starts Here ##########

# This script is designed to loop through a list of servers in an external file and check the
# space on each drive associated with each server, report on their free disk space percentage
# and output the results to the screen (if run in a console), and an email (which will only get
# sent if the parameter of WarningsOnly is passed when executing the script
# e.g. C:\WINDOWS\system32\windowspowershell\v1.0\powershell.exe -command "C:\ServerDiskSpaceChecker\ServerDiskSpaceChecker.ps1 WarningsOnly").
#
# Acknowledgements:
# DISK SPACE PORTION OF THE SCRIPT TAKEN AND MODIFIED FROM: http://www.youdidwhatwithtsql.com/check-disk-space-with-powershell-2/195
# EMAIL PORTION OF THE SCRIPT TAKEN AND MODIFIED FROM: http://www.techrepublic.com/blog/window-on-windows/send-an-email-with-an-attachment-using-powershell/4969
# EMAIL HTML FORMATTING PORTION OF THE SCRIPT TAKEN AND MODIFIED FROM: http://exchangeserverpro.com/powershell-send-html-email
# SCHEDULING PROCESS (no code required but good to know) TAKEN FROM: http://dmitrysotnikov.wordpress.com/2011/02/03/how-to-schedule-a-powershell-script/



# MODIFY THE BELOW VARIABLES AS REQUIRED

# Issue warning if % free disk space is less
$percentWarning = 15;

# Get server list (text file containing server names)
$servers = Get-Content "C:\ServerDiskSpaceChecker\computers.txt";

# IP address of email server
$smtpServer = "10.10.0.999"

# Email address to send notifications to
$emailTo = "ITTeam@yourcompanyhere.com.au"



# LET'S CHECK SOME DISKS!

$warningsExist = $false;
$scriptParameter = $args[0];
$msgSubject = "Server Disk Space Checker Results";
$datetime = Get-Date -Format "yyyyMMddHHmmss";

# variable for storing body of email (formatted in html so that we can use pretty colours)
$emailBody = "<p>The following are the results of an automated check of remaining server disk space (Refer to the <a href='http://yourwikiurlhere'>wiki</a> for more information). Entries will be marked in red if they fall below the current threshold - please investigate these ASAP.<p>";

# Add headers to log file
Add-Content "$Env:USERPROFILE\server disks $datetime.txt" "server,deviceID,size,freespace,percentFree";

foreach($server in $servers)
{
# Get fixed drive info
$disks = Get-WmiObject -ComputerName $server -Class Win32_LogicalDisk -Filter "DriveType = 3";

foreach($disk in $disks)
{
$deviceID = $disk.DeviceID;
[float]$size = $disk.Size;
[float]$freespace = $disk.FreeSpace;

$percentFree = [Math]::Round(($freespace / $size) * 100, 2);
$sizeGB = [Math]::Round($size / 1073741824, 2);
$freeSpaceGB = [Math]::Round($freespace / 1073741824, 2);

$colour = "Green";
if($percentFree -lt $percentWarning)
{
$colour = "Red";
$warningsExist = $true;
}

# Get results
$results = "$server $deviceID percentage free space = $percentFree% (Total Size=$sizeGB GB, Total Free=$freeSpaceGB GB)"

# Write results to email
$emailBody = $emailBody + "<span style='color:$colour'>$results</span><br />"

# Write results to screen
Write-Host -ForegroundColor $colour $results;
}
}



# Send an email with the details

if(($warningsExist -eq $true) -or ($scriptParameter -ne "WarningsOnly"))
{
Add-PSSnapin Microsoft.Exchange.Management.Powershell.Admin -erroraction silentlyContinue;

$msg = new-object Net.Mail.MailMessage;
$smtp = new-object Net.Mail.SmtpClient($smtpServer);
$msg.From = "system@yourcompanynamehere.com.au";
$msg.To.Add($emailTo);
if($warningsExist -eq $true)
{
$msgSubject = "Warnings Exist - " + $msgSubject;
}
$msg.Subject = $msgSubject;
$msg.IsBodyHTML = $true;
$msg.Body = $emailBody;
$smtp.Send($msg);
}


############## End of Script ##########

To enable the PowerShell script to run permissions needed to be changed on the server to allow local scripts to run ok (but external scripts require signing) - run this command in PowerShell:

  • Set-ExecutionPolicy RemoteSigned
There are a number of variables within the script that can be modified:
  • $percentWarning (Issue warning if % free disk space is less)
  • $servers (text file containing server names))
  • $smtpServer (IP address of email server)
  • $emailTo (Email address to send notifications to)
To specify which servers to check, create a file (e.g. computers.txt) and simply have a list of server names, one on each row, no empty rows at the end, and ensure the $servers variable uses that file.

When executing the script, if you want it to only email you when there is a warning then pass the parameter "WarningsOnly" after the script name (provide any other value if you want to always be notified) e.g. C:\WINDOWS\system32\windowspowershell\v1.0\powershell.exe -command "C:\Server Disk Space Checker\ServerDiskSpaceChecker.ps1 WarningsOnly"

To troubleshoot run the script/scheduled task manually - if you include the -NoExit flag when running the script it will not close the PowerShell window when it's done so you can see any errors etc that may have occurred.
    Common problems include:
    • Incorrect server name in computers.txt
    • Blank lines in computers.txt
    • Incorrect script permissions (refer above)
    • NaN% in the email (this means the script has had a problem reading the disk space - NaN stands for Not a Number - add the -NoExit flag and see what's happening).
    This script is basically a Frankenstein compliation of a few different scripts (refer to the URL references within the script itself) - I'm definitely no PowerShell expert, but it seems to do a good job when combined with the schedule task and parameters. (apologies for the blog formatting - I'm really not a big fan of the Blogger formatting!).