Avatar

Blog (pg. 17)

  • Published on
    According to RFC3986, the path portion of a URI is case-sensitive. Google and other search engines use this ruling in their crawlers, so that pages which differ by case are treated as different pages. This can be problematic when working in a non-case sensitive environment, such as IIS on Windows, because this rule is ignored and any variation in case will still return the same page. This leads to duplicate content issues and can also dilute the number of inbound links detected to a particular page. As a general rule, I always keep my internal site links in lower case, but you cannot control how 3rd parties link to you, so if someone links to you using upper case and this link is spidered by a search engine, the page could be mistaken as a duplicate of its lower case alternative. E.g. http://www.craigwardman.com/blog/ http://www.craigwardman.com/Blog/ http://www.craigwardman.com/bLoG/ These all serve up the same page, but as per the RFC should be treated as 3 different pages. We want to prevent these links from being mistaken as different pages, by redirecting requests for pages with uppercase letters to the lowercase equivelant. In my particular case, I have two other problems to overcome because I am using a shared hosting environment. Firstly, I cannot use HttpHandlers on the server so my fix had to be in the code-behind of any pages I want to apply it to (limiting it to aspx files). Secondly, the shared host uses 'Default.aspx' and not 'default.aspx' as the default page, so this causes an uppercase character in the RawUrl when the root URL is requested. The following code should be placed in your page_load event:
    
    'prevent wrong case urls
    If Text.RegularExpressions.Regex.IsMatch(Request.Url.PathAndQuery.Replace("Default.aspx", "default.aspx"), "[A-Z]") Then
     Response.StatusCode = 301
     Response.Status = "301 Moved Permanently"
    
     Dim loc As String = Request.Url.PathAndQuery.ToLower()
     If loc.EndsWith("default.aspx") Then loc = loc.Remove(loc.LastIndexOf("default.aspx"), "default.aspx".Length)
     Response.AddHeader("Location", loc)
    End If
    
    The code basically uses a 301 redirect to inform the client (spider/browser) to use the lower case equivelant of the URL. Another SEO measure is to not reveal 'default.aspx' as part of the URL (because that would be a duplicate of '/') - so the code also makes sure not to use this page name in the URL.
  • Published on
    When you have an asp:DropDownList which is *not* databound, (i.e. you add the items manually) there are a few things you have to do to get 'SelectedValue' to work. ASP.NET will ignore the 'SelectedValue' when you manually add items unless you make a call to 'DataBind' on the drop down list, so you need to add this line of code to force ASP.NET to select the correct item. However, because the 'DropDownList.Items.Add()' function will accept a string, its all too tempting to pass in the string you want to display when adding the items, but this will cause an error when you try to databind! e.g.
    ddlYear.Items.Clear()
    For yr As Integer = Now.Date.Year - 100 To Now.Date.Year - 18
       ddlYear.Items.Add(yr.ToString())
    Next
    
    ddlYear.DataBind() '<-- This part tries to load SelectedValue - but will error!
    
    Error: 'DropDownList' has a SelectedValue which is invalid because it does not exist in the list of items. What you need to do, is pass in 'new ListItem()' to the Items.Add() function and specify that the string is the 'text' and 'value' for the list item. e.g.
    ddlYear.Items.Clear()
    For yr As Integer = Now.Date.Year - 100 To Now.Date.Year - 18
      ddlYear.Items.Add(New ListItem(yr.ToString(), yr.ToString()))
    Next
    
    ddlYear.DataBind()
    You should find that the drop down list will now load with your manually added items and will display the correct 'SelectedValue'!
  • Published on
    When you store information using a key based storage mechanism, such as is provided by the 'Session' and 'ViewState' objects, you want to avoid referencing these directly by key name, as this means all references to the data need to be casted, leaving you liable to invalid cast exceptions, it means it is up to the developer to remember the key names and also leaves you liable to typos, spelling mistakes and accidental re-use of the same key more than once. The first step to solving this problem is to create a strongly typed representation of the data, using classes and properties and then have those properties simply persist their data to/from the chosen data store. As a very basic example, lets say you want to ask a user for their name and age and store it in the Session.
    Namespace Example.SessionObjects
        Public Class CurrentUserDetails
            Public Shared Property Name() As String
                Get
                    Return CStr(HttpContext.Current.Session("CurrentUserName"))
                End Get
                Set(ByVal value As String)
                    HttpContext.Current.Session("CurrentUserName") = value
                End Set
            End Property
    
            Public Shared Property Age() As Integer
                Get
                    Return CInt(HttpContext.Current.Session("CurrentUserAge"))
                End Get
                Set(ByVal value As Integer)
                    HttpContext.Current.Session("CurrentUserAge") = value
                End Set
            End Property
        End Class
    End Namespace
    If you are using ViewState, you will declare these properties as part of your UserControl, for example. Because you are wrapping access to the data as you would a private field, you gain all the benefits of that, such as validation, initialization, default values etc. As you can see, the above example still relies on the developer coming up with a unique key for each object, which is the problem addressed by this article. By nature, all of your strongly typed properties will be unique, since they live in a unique namespace, classname, propertyname - anything else would give you a compiler error. So you can use this unique name as your key name. The following code shows how you can use the 'StackFrame' object to get a reference to the currectly executing method (which will get get_PropertyName, or set_PropertyName) and use (along with name of the declaring type) to generate a unique key for the data store:
    Public Property Thing() As Integer
            Get
                    Dim sfm As Reflection.MethodBase = New StackFrame().GetMethod()
                    Return CType(HttpContext.Current.Session(sfm.DeclaringType.FullName &amp; sfm.Name.Remove(0, 4)), Integer)
            End Get
            Set(ByVal value As Integer)
                    Dim sfm As Reflection.MethodBase = New StackFrame().GetMethod()
                    HttpContext.Current.Session(sfm.DeclaringType.FullName &amp; sfm.Name.Remove(0, 4)) = value
            End Set
    End Property
    The reason I have used '.Remove(0, 4)' is to get rid of the 'get_' and 'set_' prefixes, so that the getter and setter function refer to the same key.
  • Published on
    When using a MultiLine textbox inside an ASP.NET AJAX update panel, you may encounter problems with carriage return line feeds in your text on the server using Firefox (and potentially other browsers). Internet Explorer uses the Windows style CrLf (13 10) for newlines in a textarea but Firefox uses Unix style Lf (10) only. On a synchronous postback it seems ASP.NET fixes this and you will get CrLf in your text on the server. However, when you are posting back asynchronously using AJAX, you only get Lf in your text when Firefox is used. In order to clean this up and have consistant data, I wrote a simple regex replace to make sure all Lf are preceded by a Cr.
    public static string CleanUnixCrLf(string textIn)
    {
       //firefox only uses Lf and not CrLf
       return System.Text.RegularExpressions.Regex.Replace(textIn, "([^\r])[\n]", "$1\r\n");
    }
  • Published on
    I've had this problem many a time and every time I get it again I forget what the cause was; This time I'm going to blog it!

    I have encountered three similar situations where ASP.NET fires the page_load event twice, or more.

    1. In one scenario (on a postback) your page load gets called once for the postback (IsPostback = true) and then gets called again, with IsPostback = false, which can cause a world of mistery until you realize what is happening!



      This is normally caused when using a GridView with an asp:ButtonField using ButtonType=Image. This is bug in the .NET framework which can be solved by using an asp:TemplateField with an asp:ImageButton defined within it.

    2. Another scenario is you get two or more page loads everytime you load the page, whether it is a postback or not. This is usually caused by one of your HTML elements referencing "" (blank) or "#" as a source for its data (e.g. image src="", or background="#ff0000"). This is because this source string is interpreted by the browser as a relative url (which will be the current page) and therefore the browser makes a request for the page for each HTML element that 'references' it.

      To workaround this issue, make sure your images etc. reference at least something, even if it is '%20'. Also use CSS for any colouring, not HTML attributes.

    3. Another example I have found more recently, is when using as asp:ImageButton within a template field of a databound control, where the imageUrl (src) does not exist on the server (404) will cause firstly two postbacks both with IsPostback=true, but will not raise the click event. This problem does not exist in IE, but does in Firefox and potentially other browsers. To fix this, make sure that the image source of an image button exists on the server.