MVC Buddy Class

26 January 2010 5 comments

My latest “toy” is the new .NET 4.0 and MVC framework which has a few really nice additions.  In this post I will briefly cover one of them, Buddy Classes.  Although there are disagreements as to whether or not this is a good or bad thing, I like the fact that it makes life a bit simpler on smaller projects.

First off I created a VERY simple database with one table.

CREATE TABLE [dbo].[Friend](
	[UserId] [int] IDENTITY(1,1) NOT NULL,
	[FirstName] [nvarchar](100) NOT NULL,
	[LastName] [nvarchar](100) NOT NULL,
	[BirthDate] [date] NOT NULL,
 CONSTRAINT [PK_Friend] PRIMARY KEY CLUSTERED 
(
	[UserId] ASC
)WITH (PAD_INDEX  = OFF, STATISTICS_NORECOMPUTE  = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS  = ON, ALLOW_PAGE_LOCKS  = ON) ON [PRIMARY]
) ON [PRIMARY]

GO

This table will just hold a list of people.  The BrithDate column is not really for this post but for a later one where I will demonstrate how to use user controls for editing specific data types.

Then I added my ADO.NET Entity Data Set, in my Model folder, which created an object for this table.  As we all know, if I change this class the changes will be replace every time it is re-generated.  This is a problem when you want to add some attributes to the class.  What the guys at Microsoft has done is given us a way (hacky some say) to get passed this problem.   With this new feature we can simply create a Buddy Class (normal class in the Model folder) and decorate it to look like this:

using System;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel;

namespace MVCBuddyClass.Models
{
    [MetadataType(typeof(FriendMeta))]
    public partial class Friend
    {
    }

    public class FriendMeta
    {
        [DisplayName("First Name")]
        public string FirstName { get; set; }
        [DisplayName("Last Name")]
        public string LastName { get; set; }
        [DisplayName("Date of Birth")]
        public DateTime BrithDate { get; set; }
    }
}

In this code you will notice the public partial class Friend, which has the same name as the partial class generated by the Entity Framework.  My own Friend class in this case gets the attribute [MetadataType(typeof(FriendMeta))] which you need using System.ComponentModel.DataAnnotations; for.  This hooks it up to my “buddy” class, FriendMeta.
FriendMeta contains all the properties with their attributes that we want to set, like DisplayName("First Name")] in this case.  There are many other attributes that you can set which more of will be covered in later posts, this one was simply to display the basic idea of a buddy class.

This should only be used for classes that get auto-generated to keep DRY.

64bit FastCGI installation and troubleshooting on 32bit IIS

26 November 2009 Leave a comment

This is just a short post for some of the guys out there going through what I went through today.

When following the post on the iis.net website for installing and configuring FastCGI for IIS they forgot to mention tiny little details, well, for when you are installing it on a Windows 2003 64bit machine that is running 32bit IIS.

When following their guide, please note that they misspelled the name of the fcgiext.ini (fcigext.ini).  This caused a bit of confusion as to whether mine was right.

So, to install FastCGI on a 64bit platform that is running IIS in 32bit:
Note: replace values as per your setup

  1. Download the x64 FastCGI from http://www.iis.net/extensions/fastcgi
  2. Install FastCGI
  3. Setup your PHP to run as CGI (Not going through all this here); MAKE SURE IT WORKS!
  4. Change to %WINDIR%\system32\inetsrv in command prompt
  5. Run: cscript fcgiconfig.js -add -section:"PHP" -extension:php -path:"C:\PHP\php-cgi.exe"
  6. Copy fcgiext.ini from %WINDIR%\system32\inetsrv to %WINDIR%\sysWOW64\inetsrv
  7. Create a script map for .php to %WINDIR%\SysWOW64\inetsrv\fcgiext.dll in inetmgr
  8. Add %WINDIR%\SysWOW64\inetsrv\fcgiext.dll to the FastCGI Handler in web service extensions; Default is from %WINDIR%\system32\inetsrv

Steps 6 and 8 were “kindly” left out by the iis.net website explaining how to install it and that was where my problems crept in.

Here are some errors I encountered and the cause:

Page not found (404)
IIS cannot access fcgiext.dll; check permissions, that path/file exist (quotes around it if it contains spaces) and the it is allowed in the web service extensions in IIS (check required files, make sure it’s WOW64 one as well as system32)

FastCGI Error – File not found (500)
FastCGI cannot access one of the files it requires; make sure the %WINDIR%\SysWOW64\inetsrv\fcgiext.ini file exists and that its contents point to your php-cgi.exe path and file (double check spelling)

Note: Windows is running 64bit apps from system32 and 32bit apps from sysWOW64.

I found this page just now, will help you if you require a more advanced setup: http://technet.microsoft.com/en-us/library/dd450377(WS.10).aspx

Hopefully this would be sufficient to help someone.

Client side caching

4 October 2009 5 comments

One thing that gets neglected a lot these days is the optimisation of websites.  I never bothered with it much until fairly recently.

In this post I will cover client side caching.  That is when the page gets stored on the clients browser and only updated when that page actually changes.  This can all be set up in IIS, however we do not always have access to the server or more often have dynamic pages. 

Note: IIS7 is needed in order to access the Response headers so this only works with IIS7

HTTP 1.1 specifications sets out the rules by which servers and clients must abide.  Basically who can cache content, how long for and when it was cached.  The client will then tell the server what version of the content it has cached and the server will then either return a HTTP status 304 ("Not modified") or reprocess the request.

Firstly we will create an ETag.  This will uniquely identify each page.  The HTTP 1.1 specification expects double quotes around the ETag.  Also, SetETag method can also only be called once, a second call will cause an exception. If “cacheability” is set to “private”, the header will not be added and you will have to set it using the HttpRequest’s AppenHeader method.

If contents is cached, the client will send a conditional GET request, meaning the request will contain the If-Modified-Since header amongst others.  This corresponds to Last-Modified header for the response.  The HTTP 1.1 specification says that both the ETag and last modification date must be processed by the server if both is contained in the request.

Here is the code that I have.  First is for creating the ETag, which as you can see is a MD5 hash of the filename and last modified date.  This code might not be the most efficient so please review it before using it in a live environment.  I copied this code and translated it from a Delphi blog just to a state where it works.

private string GetFileETag(string fileName, DateTime modifyDate)
{
    string FileString;
    System.Text.Encoder StringEncoder;
    byte[] StringBytes;
    MD5CryptoServiceProvider MD5Enc;
    //use file name and modify date as the unique identifier
    FileString = fileName + modifyDate.ToString("d", CultureInfo.InvariantCulture);
    //get string bytes
    StringEncoder = Encoding.UTF8.GetEncoder();
    StringBytes = new byte[StringEncoder.GetByteCount(FileString.ToCharArray(), 0, FileString.Length, true)];
    StringEncoder.GetBytes(FileString.ToCharArray(), 0, FileString.Length, StringBytes, 0, true);
    //hash string using MD5 and return the hex-encoded hash
    MD5Enc = new MD5CryptoServiceProvider();
    return "\"" + BitConverter.ToString(MD5Enc.ComputeHash(StringBytes)).Replace("-", string.Empty) + "\"";
}

Here we check whether the ETag is in the request and then take the appropriate action.  Please note the hardcoded date, replace this with your last updated date for your page

//REPLACE THIS WITH ACTUAL DATE AND FILENAME
DateTime updated = DateTime.Parse("01/10/2009");
string filename = "Default.aspx";

DateTime modifyDate = new DateTime();
//see if we got a valid date
if (!DateTime.TryParse(Request.Headers["If-Modified-Since"], out modifyDate))
{
    modifyDate = DateTime.Now;
}
//get request's etag
string eTag = Request.Headers["If-None-Match"];
//check if we got an etag
if (string.IsNullOrEmpty(eTag))
{
    //get new etag
    eTag = GetFileETag(filename, updated);
}

//check if the file had been modified
if (!IsFileModified(filename, modifyDate, eTag))
{
    //no change, return 304
    Response.StatusCode = 304;
    Response.StatusDescription = "Not Modified";
    //set to 0 to prevent client waiting for data
    Response.AddHeader("Content-Length", "0");
    //has to be not Private
    Response.Cache.SetCacheability(HttpCacheability.Public);
    Response.Cache.SetLastModified(modifyDate);
    Response.Cache.SetETag(eTag);
    Response.End();
    return;
}
else
{
    //make sure the client caches it
    Response.Cache.SetAllowResponseInBrowserHistory(true);
    Response.Cache.SetCacheability(HttpCacheability.Public);
    Response.Cache.SetLastModified(modifyDate);
    Response.Cache.SetETag(eTag);
}

The above coded was added to my Page_Load event. The 304 should be returned before any content.

private bool IsFileModified(string filename, DateTime modifyDate, string eTag)
{
    bool fileDateModified;
    DateTime modifiedSince;
    TimeSpan modifyDiff;
    bool eTagChanged;

    //assume file has been modified unless we can determine otherwise
    fileDateModified = true;

    //Check If-Modified-Since request header, if it exists 
    if (!string.IsNullOrEmpty(Request.Headers["If-Modified-Since"]) 
        && DateTime.TryParse(Request.Headers["If-Modified-Since"], out modifiedSince))
    {
        fileDateModified = false;
        if (modifyDate > modifiedSince)
        {
            modifyDiff = modifyDate - modifiedSince;
            //ignore time difference of up to one seconds to compensate for date encoding
            fileDateModified = modifyDiff > TimeSpan.FromSeconds(1);
        }
    }

    //check the If-None-Match header, if it exists, this header is used by FireFox to validate entities based on the etag response header 
    eTagChanged = false;
    if (!string.IsNullOrEmpty(Request.Headers["If-None-Match"]))
    {
        eTagChanged = Request.Headers["If-None-Match"] != eTag;
    }
    return (eTagChanged || fileDateModified);
}

It might be a good idea to put this into a HttpHandler for your dynamic pages/images or maybe a HttpModule.

Note: IE does not return the caching headers when you are access http://localhost/, not IE7 anyway.  Took me about an hour to find the problem.

I hope this is clear enough.  If not, comment and I will try and answer all questions.

Onkeyup delay in JavaScript

18 September 2009 3 comments

In a recent post I used a little script to delay the onkeyup event for a set amount of milliseconds after the key was released.  That way a callback will not be made for every key that gets released, but only once the user stopped typing.  That post was brought up many times by a search for this specific function, so I decided to extract it into its own post.

For this post I will only show an alert message to keep it to the bare minimum.

First we need to add this to our page, preferably in the <head> section.  Have a look here if you are not familiar with XHTML compliant JavaScript, it’s a short and straight to the point post.

<script language="javascript" type="text/javascript">
    //<![CDATA[
    function delayTimer() {
        var timer;
        return function(fun, time) {
            clearTimeout(timer);
            timer = setTimeout(fun, time);
        };
    }
    var delayFunction = delayTimer();

    function showMessage() {
        alert('Delay expired!');
    }
    //]]>
</script>

This is just a timer which restarts itself every time the onkeyup event triggers.  Once it runs out it will execute the function that you specify later.

<input id="txtDelay" type="text" onkeyup="delayFunction(showMessage, 500);" />

This is our input box which uses the onkeyup delay.  We use our delegate that we created and pass in two parameters, the first is the function to call and the second is the delay before doing so.  In this case it will wait 500ms before calling the showMessage function.

That is all you need to get a delay for an event in JavaScript.

NOTE: This does NOT work when pasting text in using the mouse.  Disable right-click on the input box if you rely on this function to execute.

Hope this is clear.  Comment if there is anything unclear or if you have a better way for me to do things.

Update – Passing in a parameter to the function as requested by a comment

<script language="javascript" type="text/javascript">
    //<![CDATA[
    function delayTimer() {
        var timer;
        return function (fun, time) {
            clearTimeout(timer);
            timer = setTimeout(fun, time);
        };
    }

    var delayFunction = delayTimer();

    // Takes parameters to display
    function showMessage(message, sender) {
        alert(message + " - " + $(sender).val());
    }
    //]]>
</script>
<!-- Now passing in a function with parameters -->
<input id="txtDelay" type="text" onkeyup="delayFunction(function() { showMessage('Display this string!', $('#txtDelay')); }, 500);" />

Note that passing in ‘this’ to the function in the onclick event does not send the input through.

jQuery and ajax calling webservice

30 August 2009 Leave a comment

In my previous post I wrote a domain name availability checker.  In this post I will create the front-end for it to display the availability using jQuery and ajax.  Note that I changed the web service slightly for this post.  The service will now return ‘available’ or ‘not available’.

I will not be doing much in the sense of validation and will leave that up to you when implementing code.

First off I will add a new HTML (can do webform) file to my solution.  If you do not have jQuery, get it here and rename the downloaded file from .download to .js, then add them to your solution (right-click on the project and click ‘add existing file’, then browse to your jQuery download and add the file.  See image below).

addFile

You can drag the script files from the solution explorer into the header section of your page or add them yourself.  You should then have something along this line:

<head>
    <title>My domain checker</title>
    <script src="jquery-1.3.2.min.js" type="text/javascript"></script>
</head>

We need a text field where the domain will be entered.  If you are not using a plain HTML input, then you will have to do a clever thing later to get the .NET generated name using <%= TextBox1.ClientId %>, so I recommend using a HTML text input if possible.  Add an onkeyup event, I am using onkeyup="getAvailability(displayAvailability, 500);".  The 500 is the delay in ms after the user stopped typing, but more about this later.

Add the following javascript to the page:

    function getAvailabilityTimer() {
        var timer;
        return function(fun, time) {
            clearTimeout(timer);
            timer = setTimeout(fun, time);
        };
    }
    var getAvailability = getAvailabilityTimer();

This will act as the timer since the user stop typing until the ajax callback is made.  You  can change this time by altering the number (500) in the onclick event for the textbox we created above.

The final function is the one that will be doing all the work.  Here is what mine looks like:

function displayAvailability() {
    var domain = $('#domainName').val();
    var selector = "result";//easy to change later
    $.ajax({
        type: "POST",
        contentType: "application/json; charset=utf-8",
        url: "DomainService.asmx/CheckDomain", //our service name with the method
        data: "{'domain' : '" + domain + ".com'}", //parameter for which to pass data
        dataType: "json", //responde type
        success: function(msg, success) {
            if (success == 'success') {
                $('#' + selector).html('Domain is ' + msg.d); //good response
            } else {
                $('#' + selector).html('Not able to check at the moment.  Please try again later.'); //service returned an error
            }
        },
        error: function(msg, success) {
            $('#' + selector).html('Not able to check at the moment.  Please try again later.'); //error with ajax call
        }
    });
}

One last thing before testing, in the web service, make sure to uncomment the [System.Web.Script.Services.ScriptService] attribute.  Then let’s start it up and try it out.

checker

And there you have it, working like a charm…I will leave the nice graphicy stuff to you!

When I was testing it the first time, I ran into some problems.  I used nikhilk’s web developer helper.  It is a MUST have when working with ajax.  The problem was that I forgot to set the ScriptService attribute *blush*.

Here is my whole pages’ source: (is the ‘ used right? hope so :))

<head>
    <title>My domain checker</title>
    <script src="jquery-1.3.2.min.js" type="text/javascript"></script>
    <style type="text/css">
        body 
        {
            line-height: 25px;
            vertical-align: middle;
        }
        label
        {
            padding-right: 5px;
        }
    </style>
    <script type="text/javascript" language="javascript">
        function getAvailabilityTimer() {
            var timer;
            return function(fun, time) {
                clearTimeout(timer);
                timer = setTimeout(fun, time);
            };
        }
        var getAvailability = getAvailabilityTimer();

        function displayAvailability() {
            var domain = $('#domainName').val();
            var selector = "result";//easy to change later
            $.ajax({
                type: "POST",
                contentType: "application/json; charset=utf-8",
                url: "DomainService.asmx/CheckDomain", //our service name with the method
                data: "{'domain' : '" + domain + ".com'}", //parameter for which to pass data
                dataType: "json", //responde type
                success: function(msg, success) {
                    if (success == 'success') {
                        $('#' + selector).html('Domain is ' + msg.d); //good response
                    } else {
                        $('#' + selector).html('Not able to check at the moment.  Please try again later.'); //service returned an error
                    }
                },
                error: function(msg, success) {
                    $('#' + selector).html('Not able to check at the moment.  Please try again later.'); //error with ajax call
                }
            });
        }
    </script>
</head>

<body>
<div id="result"></div>
<label for="domainName">Domain:</label><input id="domainName" type="text" onkeyup="getAvailability(displayAvailability, 500);" />.com
</body>
</html>

All feedback is appreciated.

Domain name availability checker

25 August 2009 3 comments

I was puzzled about how they do domain name availability checks for some time now.  It was only when I actually needed it for a website that I am doing that I actually looked into this.  This blog entry will cover the basics of getting a domain checker up and running, using a web service to do the actual checking.  I will cover the Ajax part in a next post.

The theory behind it all is actually really simple;  connect to a whois server and do a check on that domain, then check the response for a specific string and voila, we know whether the domain had been registered or not.

First off  create a new ASP.NET project.  Then add a new web service, I called mine DomainService.asmx.

You now got a brand new service with a default HelloWorld method, so the first thing we need to do is replace that with our own method, in this case I’ll call it CheckDomain and pass the domain to check through as a string.

First thing I do is check whether we have some text passed in, if not, throw and exception so that the calling client can handle it.  You might want to do a check first to see whether it is someone else trying to use your service but I won’t cover that here to keep it simple.

Next we need to create a new TcpClient instance.  You should notice when you type the CaSe right, you will get a red accelerator, then click on it (or hit ctrl+. [full stop]) and you will see what appears in the image below, select the using System.Net.Sockets; option.  You can manually add it to the “uses” at the top of the page, but that involves effort :).

AcceleratorOpen

The next thing to do is create a byte[] of the domain and ASCII encode it.  We also need to add the new line and line feed characters to the end of that.

//convert to byte[] to send over stream    
byte[] byteDomain = Encoding.ASCII.GetBytes(domain + "\r\n"); 

Now that we have the domain in an ASCII byte array we can send it to the server, but first we need to create the connection.  Also, a stream is needed to write to the server using our TcpClient connection so we create this as well.  Now lets open the connection, get the stream, write our domain to it, read the response and then close the connection.  I also included some debugging text so we can see the response and make life a little bit easier.

//create connection 
tcpClient = new TcpClient("YOUR_WHOIS_SERVER", 43); 
//get stream
Stream objStream = tcpClient.GetStream(); 
//write to the stream
objStream.Write(byteDomain, 0, byteDomain.Length);
//get response
StreamReader objReader = new StreamReader(tcpClient.GetStream(), 
    Encoding.ASCII); 
//read the response 
response = objReader.ReadToEnd();
#if DEBUG 
    Debug.WriteLine("--[" + domain + "]--"); 
    Debug.WriteLine(response); 
    Debug.WriteLine("--------"); 
#endif 
//close connection 
tcpClient.Close(); 

We now have a response from the server if nothing had gone wrong.  The response can now be analysed for the string.  Note that this response string I am talking about is different for most servers.  Just check through your response and you will find a common string.

//string to return
string returnValue = string.Empty;
//see if it is registered or not
if (Regex.IsMatch(response, "No match for ", RegexOptions.IgnoreCase))
{
    //yes it is registered
    returnValue = "available";
}
else if (Regex.IsMatch(response, "Registrar:", RegexOptions.IgnoreCase))
{
    //no it is not registered
    returnValue = "registered";
}
else
{
    //maybe some error returned from the server, i.e. disconnected 
    throw new Exception("Unknown response from server");
}
return returnValue;

As you probably noticed by now, I love throwing exceptions.  This makes life an awful lot easier when something goes wrong.

Now let’s test this.  Running the webservice (http://localhost:1929/DomainService.asmx in my case) by hitting F5 in Visual Web Developer brings you to the domain page.  Click on the CheckDomain option and enter a test domain (microsoft.com).  If you used the same server as me then you would have gotten (are my tenses right?) an exception, just try a different domain since loads of microsoft.com.* domains exists.  Our company domain works perfect for me, it returns “registered”.

Here is my full source code for this web service

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Services;
using System.Net.Sockets;
using System.Text;
using System.IO;
#if DEBUG
    using System.Diagnostics;
using System.Text.RegularExpressions;
#endif

namespace DnsChecker
{
    /// <summary>
    /// Summary description for DomainService
    /// </summary>
    [WebService(Namespace = "http://tempuri.org/")]
    [WebServiceBinding(ConformsTo = WsiProfiles.BasicProfile1_1)]
    [System.ComponentModel.ToolboxItem(false)]
    // To allow this Web Service to be called from script, using ASP.NET AJAX, uncomment the following line. 
    // [System.Web.Script.Services.ScriptService]
    public class DomainService : System.Web.Services.WebService
    {

        [WebMethod]
        public string CheckDomain(string domain)
        {
            //some error checking
            if (string.IsNullOrEmpty(domain))
                throw new ArgumentNullException(domain, "Domain was not specified");

            //will contain the response from the server
            string response = string.Empty;
            TcpClient tcpClient = null;
            try
            {
                //convert to byte[] to send over stream
                byte[] byteDomain = Encoding.ASCII.GetBytes(domain + "\r\n");
                //create connection
                tcpClient = new TcpClient("whois.crsnic.net", 43);//possible exception that extension is not in list
                //get stream
                Stream objStream = tcpClient.GetStream();
                //write to the stream
                objStream.Write(byteDomain, 0, byteDomain.Length);
                //get response
                StreamReader objReader = new StreamReader(tcpClient.GetStream(), Encoding.ASCII);
                //read the response
                response = objReader.ReadToEnd();

#if DEBUG
                Debug.WriteLine("--[" + domain + "]------------------");
                Debug.WriteLine(response);
                Debug.WriteLine("---------------------------------------");
#endif
            }
            catch (Exception ex)
            {
#if DEBUG
                Debug.WriteLine("--[" + domain + "]------------------");
                Debug.WriteLine("ERROR FOR " + domain + ex.Message);
                Debug.WriteLine("---------------------------------------");
#endif
                //maybe send some mail in case the whois server is down
                throw;//throw the error after writing the debug info so the client can deal with it
            }
            finally
            {
                //close connection
                tcpClient.Close();
            }

            //string to return
            string returnValue = string.Empty;
            //see if it is registered or not
            if (Regex.IsMatch(response, "No match for ", RegexOptions.IgnoreCase))
            {
                //yes it is registered
                returnValue = "available";
            }
            else if (Regex.IsMatch(response, "Registrar:", RegexOptions.IgnoreCase))
            {
                //no it is not registered
                returnValue = "registered";
            }
            else
            {
                //maybe some error returned from the server, i.e. disconnected 
                throw new Exception("Unknown response from server");
            }
            return returnValue;
        }
    }
}

There you have it, a skeleton web service that checks whether a domain is available or not. You should really update this code and make it safer.  In a next post I will connect this service up to a nice Ajax front end to dynamically check the domain as the user enters it, using jQuery.

This is not guaranteed to be the best way of doing things, but they work for me and others.  Your comments, good or bad, would be greatly appreciated as it helps and inspires me to improve.

Playstation 3 Slim

19 August 2009 Leave a comment

Reading the news this morning I found that the new PS3 Slim was released.  I was really excited about this, especially the fact that it has got a matt black finish.

As you can see in the image above, it looks awfully much like a STB; I am however pretty sure that it will look a look better in real life.

So, I headed over to the official website and delve a bit deeper.  Comparing it to my current system (original 60gig with hardware backwards compatibility support) I don’t think that I will replace the one I have.

Some of the features (or lack thereof) include:

  • Bravia® Sync™ – when connected to a Bravia TV, your PS can be controlled using the TV’s remote control and the PS will also turn off with the TV
  • Vertical stand has to be bought separately
  • Other operating systems are no longer supported
  • 32% smaller, 36% lighter and  34% more energy efficient according to www.amazon.co.uk
  • 120GB hard drive (where 1KB = 1000bytes, not the standard 1024)
  • 2 USB ports
  • No memory card readers
  • No mention of backwards compatibility with PS1/2 games

Being released in September 2009, just in time for Christmas, it would make the perfect gift. Amazon is taking pre-orders (here) but there seems to be no bundles available as yet.

Please share your thoughts on the new PS3 Slim