ASP.net

ASP.net Web Application reverting to Website

This is defiantly a “Note To Self” post.

I still don’t know why it happens, but occasionally an ASP.net Web App turns itself into an ASP.net web site.

If this happens try the following:

  • Remove the .webinfo file
  • Remove any reference to the project in IIS

Simple Google Analytics ASP.net control

Google Analytics is a great tool for analysing website traffic and goal conversion. When using asp.net, Master Pages, can make implementing this easy. However if there are multiple Master Pages or many pages that aren’t derived from master pages a simple control to drop the Google scripts on a page.

The following control provides a simple way to add Google Analytics scripts to a page. Furthermore it is customisable if the need arises to override the page that is tracked, which is handy when doing goal conversion with an ASP.net page which post backs to itself.

Note that the control inherits from the control class. This ensures there are no additional tags rendered other than the script tags.

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Text;
using System.Web;
using System.Web.UI;
using System.Web.UI.WebControls;

namespace MyControls
{
    /// <summary>
    /// Web control to put basic Google Analytics on a page
    /// </summary>
    [DefaultProperty("Text")]
    [ToolboxData("<{0}:GenericGoogleAnalytics runat=server></{0}:GenericGoogleAnalytics>")]
    public class GenericGoogleAnalytics : Control
    {
        
        /// <summary>
        /// Google Identifier
        /// </summary>
        [Bindable(true)]
        [Category("Initialisation")]
        [DefaultValue("")]
        [Localizable(true)]
        public string GoogleID
        {
            get
            {
                String s = (String)ViewState["GoogleID"];
                return ((s == null) ? String.Empty : s);
            }

            set
            {
                ViewState["GoogleID"] = value;
            }
        }

        /// <summary>
        /// Page to track if not current page.
        /// </summary>
        /// <remarks>
        /// <para>This is generaly used for pages after a form is submitted.</para>
        /// <para>A ficticious url is set up in Google Analytics and used here.</para>
        /// </remarks>
        [Bindable(true)]
        [Category("Initialisation")]
        [DefaultValue("")]
        [Localizable(true)]
        [Description("Page to track if not current page")]
        public string TrackPageview
        {
            get
            {
                String s = (String)ViewState["TrackPageview"];
                return ((s == null) ? String.Empty : s);
            }

            set
            {
                ViewState["TrackPageview"] = value;
            }
        }

        /// <summary>
        /// Renders control to page
        /// </summary>
        /// <param name="output">Output writer</param>
        protected override void Render(HtmlTextWriter output)
        {
              //Prototype google script           
              string googleScript = @"  
                    var _gaq = _gaq || [];
                    _gaq.push(['_setAccount', '{0}']);
                    _gaq.push({1});

                    (function() {{
                        var ga = document.createElement('script'); ga.type = 'text/javascript'; ga.async = true;
                        ga.src = ('https:' == document.location.protocol ? 'https://ssl' : 'http://www') + '.google-analytics.com/ga.js';
                        var s = document.getElementsByTagName('script')[0]; s.parentNode.insertBefore(ga, s);
                    }})();";
            
            output.AddAttribute(HtmlTextWriterAttribute.Type, "text/javascript");
            output.RenderBeginTag(HtmlTextWriterTag.Script);           

            output.Write(string.Format(googleScript, GoogleID, GetTracking()));

            output.RenderEndTag();
        }

        //Make Sure Google ID supplied
        protected override void OnPreRender(EventArgs e)
        {
            base.OnPreRender(e);
            if (string.IsNullOrEmpty(GoogleID))
                throw new HttpException(string.Format("Google ID of {0} can not be blank", this.ID));
        }

        private string GetTracking()
        {
            string retVal = "['_trackPageview']";
            if (!string.IsNullOrEmpty(TrackPageview))
                retVal = string.Format("['_trackPageview','{0}']", TrackPageview);

            return retVal;
        }
    }
}

After adding the control to the Visual Studio tool box here is an example of usage demonstrating using a fictitious for goal tracking.

ASPX

<%@ Page Language="C#" AutoEventWireup="true" CodeBehind="GATest.aspx.cs" Inherits="HubbCom.GATest" %>

<%@ Register Assembly="MyControls" Namespace="MyControls" TagPrefix="cc1" %>

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">

<html xmlns="http://www.w3.org/1999/xhtml" >
<head runat="server">
    <title></title>
    <cc1:GenericGoogleAnalytics ID="GA1" GoogleID="UA-7419588-2" runat="server">
    </cc1:GenericGoogleAnalytics>
</head>
<body>
    <form id="form1" runat="server">
    <div>
        <asp:Button ID="Button1" runat="server" Text="Button" />
    
    </div>
    </form>
</body>
</html>

.cs

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.UI;
using System.Web.UI.WebControls;

namespace MyNameSpace
{
    public partial class GATest : System.Web.UI.Page
    {
        protected void Page_Load(object sender, EventArgs e)
        {
          
	    //Set page tracker for ficticious url
	    if (IsPostBack)
            {
                GA1.TrackPageview = "/thankyou.aspx";
            }
        }
    }
}

Possible improvements could be made by having a default Google ID stored in the web.config file and make the bounding script tags optional

Validating Checked Controls in ASP.net

This is one that pretty much every ASP.net web forms developer will come across every now and then.

What to do when you have a bunch of  check boxes or radio buttons in some kind  of common container: Table, list, even a check box list, etc, and you need to make sure at least one is checked.

One way to handle this is to use a custom validator:

<asp:CustomValidator 
     ID="custEventVal" 
     runat="server" 
     ErrorMessage="Please Select An Event"
     ClientValidationFunction="ValidateEvents" 
     EnableClientScript="true" 
     Display="Dynamic" 
     onservervalidate="custEventVal_ServerValidate">
</asp:CustomValidator>

With client side validation:

//Requires jQuery
//#SelEvent is the id of our containing element
//in this case we are checking for radio buttons
function ValidateEvents(sender, args) {
    args.IsValid = ($("#SelEvent input:radio:checked").length > 0);
}

and server side:

/// <summary>Ensure radio button has been selected</summary>
/// <param name="source"></param>
/// <param name="args"></param>
protected void custEventVal_ServerValidate(object source, ServerValidateEventArgs args)
{
    int eventID;
    //in this instance our radio buttons are set up with the name
    //rdoEvents
    args.IsValid = (!String.IsNullOrEmpty(Request.Form["rdoEvent"]));
}

This will work on when the form is submitted but the validation error will still remain regardless of if one on the controls is checked. To resolve this use jQuery to wire up ValidatorHookupControlID, which is provided by the ASP.net validation frame work.

//#SelEvent is the id of our containing element
//in this case we are checking for radio buttons
$(document).ready(function() {
   $("#SelEvent input:radio").each(function() {
       ValidatorHookupControlID($(this).attr('id'), document.all["<%= custEventVal.ClientID %>"]);
   });
 });